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

发布于 2024-11-28 01:58:06 字数 485 浏览 2 评论 0原文

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

type
  TString10 = string[10];

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

var
  sTen : TString10;

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

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

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

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

I would like to declare a C# value type that only allows strings of a particular length. Said length should be validated at compile time. This is doable in Delphi as:

type
  TString10 = string[10];

and if I use said tyoe as:

var
  sTen : TString10;

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

Now as I understand it you cannot declare a string type in C# of a fixed length. Various solutions I have seen don't offer compile time checking for C#. As I am prepared to declare my own C# value type struct is this something I can achieve with .Format()?

All help and pointers greatly appreciated.

PS. I really would like to achieve compile time checking of string length assignments, so please no "Why are you....?"

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

讽刺将军 2024-12-05 01:58:06

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

Given that System.String has this constructor overload:

public String(char[] value)

you could create your own value type like this:

public struct FixedLengthString
{
    private readonly string s;

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

This particular example would give you a string of exactly three characters, initialized like this:

var fls = new FixedLengthString('f', 'o', 'o');
溺深海 2024-12-05 01:58:06

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

If you use Spec# you can constrain various things at compile time, including string length.

月亮坠入山谷 2024-12-05 01:58:06

我有一个谜题要给你。假设您的 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;
    }
}

I have a puzzle for you. Let's assume your TString10 already exists in C#, and that a compile-time error should be raised when you assign strings that are too long:

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

TString10 foo = stringWithUnknownLength;

Should a compile-time error be raised here? And if so, how would the compiler know when to raise it?

As you see, the possibilities of compile-time checking are limited. There's some things the compiler can easily verify, such as when you assign a specific string constant to a TString10 variable. But there's a vast amount of cases where verification depends on possibly complex program logic, or on I/O, or on random numbers (like in the above example) — in all those cases, compile time checks are impossible.


I was originally going to suggest to you a combination of a wrapper class around string, combined with the static checking capabilities of Code Contracts; however, that approach would suffer from the same fundamental problem. Anyway, for completeness' sake:

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;
    }
}
忆梦 2024-12-05 01:58:06

您可以声明一个固定长度的只读字符数组。只读需要避免任何进一步的调整大小。然而,这并没有提供直接的字符串操作,但它离你想要的方式并不太远。

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.

初见你 2024-12-05 01:58:06

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

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

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

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

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

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# 编译器代替您调用的静态方法。

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

您将获得一个新的数据类型,它不同于System.String。您可以重写ToString方法,以便String10可以在String.Format和朋友中使用,您甚至可以定义扩大(隐式)转换到 System.String,以便您可以将 String10 与需要 System.String 的 API 一起使用。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文