【问题标题】:C# fixed string length - compile time checkingC# 固定字符串长度 - 编译时检查
【发布时间】:2011-10-20 15:03:40
【问题描述】:

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

type
  TString10 = string[10];

如果我使用上述 tyoe 作为:

var
  sTen : TString10;

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

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

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

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

【问题讨论】:

  • 您为什么如此热衷于编译时检查?尽管您可以在 Delphi 中执行此操作,但它是一个非常过时的功能,早已被弃用。大约 15 年前,Delphi 短弦已经过时了。
  • 马克给了你一个很好的答案。现在请您满足我的好奇心并解释原因吗?
  • @David:您可能指的是 ShortString,这与 Delphi 的长字符串支持不同,后者是字符串关键字所代表的。
  • @Jacek:我不想依靠单元测试来修复可能在编译时发现的明显错误。还说我有一个名为 TCountryCode 的类型,它是一个 3 个字符的字符串,出于某种原因,我决定将其更改为 2 个字符的字符串,在任何我错误地使用 AUS 而不是 AU 的地方(我确信我永远不会使用更多超过 2 个字符)将被编译器捕获。我认为与 Delphi 相比,C# 中的类型系统严重缺乏。您不能为类型加上别名这一事实是一个弱点恕我直言
  • @TheEdge 你显然不太了解Delphi。我指的是短字符串,实际上string[10] 是一个固定长度的短字符串。阅读有关它的所有信息here。 Delphi 中的短字符串是一种时代错误,在现代代码中从未使用过。我不明白你为什么把它当作美德的典范。不是。

标签: c# compile-time string-length


【解决方案1】:

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

【讨论】:

  • 谢谢。我会调查的。
【解决方案2】:

鉴于 System.String 有 this constructor overload:

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');

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

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

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

      您可以编写一个工具(作为编译后步骤),通过 IL 并拒绝对没有有效(即最多 10 个字符)字符串的 String10 构造函数的每次调用 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 与预期的API 一起使用System.String.

      【讨论】:

        【解决方案5】:

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

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

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

        如您所见,编译时检查的可能性是有限的。编译器可以轻松验证某些内容,例如将特定字符串 constant 分配给 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;
            }
        }
        

        【讨论】:

        • +1 for CodeContracts 这也是我的想法。非常清晰且没有代码侵入性的解决方案,具有编译时检查并且无需创建特殊类型或其他任何东西。可能很适合这个问题。
        • +1 用于解释编译时字符串长度检查的无意义。
        • 实际上有几种语言(特别是那些具有依赖类型的语言)在编译时强制字符串/数组边界,迫使程序员要么显式处理越界案例或证明它不会发生。所以,不,这并非不可能(尽管在 C# 中不太可能)。
        猜你喜欢
        • 1970-01-01
        • 2018-05-12
        • 2011-04-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多