c#固定字符串长度-编译时检查

本文关键字:编译 检查 字符串 | 更新日期: 2023-09-27 18:04:39

我想声明一个c#值类型,它只允许特定长度的字符串。所述长度应在编译时进行验证。这在Delphi中是可行的:

type
  TString10 = string[10];

如果我用said字体作为

var
  sTen : TString10;
sTen := '0123456789A';   //This generates a compile time error

在我看来,你不能在c#中声明一个固定长度的字符串类型。我所见过的各种解决方案都不提供c#的编译时检查。当我准备声明我自己的c#值类型结构时,这是我可以用.Format()实现的吗?

所有的帮助和指针非常感激。

p。我真的想实现字符串长度分配的编译时检查,所以请不要"为什么你....?"

c#固定字符串长度-编译时检查

String有以下构造函数重载:

public String(char[] value)

你可以像这样创建你自己的值类型:

public struct FixedLengthString
{
    private readonly string s;
    public FixedLengthString(char c1, char c2, char c3)
    {
        this.s = new string(new [] { c1, c2, c3 });
    }
}

这个特殊的例子会给你一个恰好三个字符的字符串,初始化如下:

var fls = new FixedLengthString('f', 'o', 'o');

如果你使用spec#,你可以在编译时限制各种东西,包括字符串长度。

我有一个难题给你。假设您的TString10在c#中已经存在,并且当您分配太长的字符串时应该引发编译时错误:

string stringWithUnknownLength = "".PadLeft(new Random().Next(0, 100));
TString10 foo = stringWithUnknownLength;

应该在这里引发编译时错误吗?如果是这样,编译器如何知道何时引发它?

如您所见,编译时检查的可能性是有限的。有一些事情编译器可以很容易地验证,例如当您将特定的字符串常量分配给TString10变量时。但是有大量的情况下,验证依赖于可能复杂的程序逻辑,或I/O,或随机数(如上面的例子)—在所有这些情况下,编译时检查是不可能的。


我本来打算给你一个围绕string的包装器类的组合,结合代码契约的静态检查功能;然而,这种方法也会遇到同样的根本问题。总之,为了完整起见:

using System.Diagnostics.Contracts;
class TString10
{
    private string value;
    …
    public static implicit operator TString10(string str)
    {
        Contract.Requires(str.Length <= 10);
        return new TString10 { value = str };
    }
    public static implicit operator string(TString10 str10)
    {
        Contract.Ensures(Contract.Result<string>().Length <= 10);
        return str10.value;
    }
}

可以声明一个固定长度的只读char数组。只读需要避免进一步调整大小。然而,这并没有提供直接的字符串操作,但它与您希望的方式相差不大。

在我看来,没有办法单独在c#中实现这一点,因为字符串字量总是 System.String s,并且因为c#类型系统完全忽略了数组的大小。

假设您使用自定义值类型(是的,您必须声明10个char字段,因为char[10]将存储在堆上),

struct String10
{
     char c0;
     char c1;
     ...
     char c9;
     public String10(string literal){...}
}

您可以编写一个工具(作为编译后步骤),它遍历IL并拒绝String10构造函数的每个调用,该构造函数没有有效(即最多10个字符)字符串literal作为其参数。

new String10("0123456789") //valid
new String10("0123456789A") //rejected
new String10(someString) //has to be rejected as well → undecidable ↔ halting problem

如果你不喜欢写new String10(...),你可以定义一个从System.StringString10的隐式转换。在底层,这将是一个由c#编译器代替你调用的静态方法。

一个允许您查看IL的库是mono.cecil.

您将获得与System.String不同的新数据类型。您可以覆盖ToString方法,以便String10可以在String.Format和朋友中使用,您甚至可以定义到System.String的扩展(隐式)转换,以便您可以将String10与期望System.String的api一起使用。