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.String
s 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
.
精彩评论