2

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

type
  TString10 = string[10];

如果我使用所说的 tyoe 作为:

var
  sTen : TString10;

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

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

非常感谢所有帮助和指示。

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

4

5 回答 5

5

鉴于 System.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');
于 2011-08-05T07:12:22.197 回答
3

如果您使用Spec#,您可以在编译时限制各种内容,包括字符串长度。

于 2011-08-05T07:12:01.223 回答
3

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

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

TString10 foo = stringWithUnknownLength;

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

如您所见,编译时检查的可能性是有限的。编译器可以轻松验证某些内容,例如将特定字符串常量分配给TString10变量时。但是在很多情况下,验证可能取决于复杂的程序逻辑、I/O 或随机数(如上面的示例)——在所有这些情况下,编译时检查是不可能的。


我原本打算向你建议一个包装类的组合string,结合Code Contracts的静态检查功能;然而,这种方法也会遇到同样的根本问题。无论如何,为了完整起见:

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;
    }
}
于 2011-08-05T07:51:45.577 回答
1

You may declare a readonly char array of a fixed length. The readonly need to avoid any further resize. However, that's not offers a direct string manipulation, but it's not too far from the way you wish.

于 2011-08-05T07:21:37.873 回答
1

The way I see it, there is no way to implement this in C# alone, because string literals are always System.Strings and because the C# type system does is completely oblivious to array sizes.

Assuming you go with a custom value type (and yes, you have to declare 10 char fields, because char[10] would be stored on the heap),

struct String10
{
     char c0;
     char c1;
     ...
     char c9;

     public String10(string literal){...}
}

You could write a tool (as a post-compilation step) that goes through the IL and rejects every invocation of that String10 constructor that doesn't have a valid (i.e. at most 10 characters) string literal as its parameter.

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

If you don't like having to write new String10(...), you could define an implicit conversion from System.String to String10 instead. Under the hoods, this would be a static method called by the C# compiler in your stead.

One library that allows you to look at IL is mono.cecil.

You will get a new data type, that is distinct from System.String. You can override the ToString method so that String10 can be used in String.Format and friends, you could even define a widening (implicit) conversion to System.String so that you can use String10 with APIs that expect a System.String.

于 2011-08-05T07:25:38.767 回答