开发者

C# fixed string length - compile time checking

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 gre开发者_StackOverflow社区atly appreciated.

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


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


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


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;
    }
}


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜