C# creating a non-nullable string. Is it possible? Somehow?

后端 未结 8 899
感情败类
感情败类 2021-01-11 10:05

So you can\'t inherit string. You can\'t make a non-nullable string. But I want to do this. I want a class, let\'s call it nString that returns a d

相关标签:
8条回答
  • 2021-01-11 10:35

    It's possible to define an "immutable"(*) struct which behaves almost exactly like a String, but has a default value that behaves like an empty string rather than null. Such a type should encapsulate a single field of type String or Object, define a narrowing conversion from String which ensures the supplied string is non-null and stores the it in its data field, and a widening conversion to String which returns an empty string if the field is null, or its ToString() value otherwise. For each public member of String, the type should define a member which invokes the corresponding member of (String)this. Such a type should also define overloads for string concatenation operators.

    (*) All value types which can hold any value which is observably different from their default are mutable, since struct1 = struct2; will mutate the instance stored in struct1 by overwriting all its public and private fields with the contents of the corresponding fields in type2, and there's nothing the structure type can do to prevent that.

    Although in most cases one would want to have such a type simply keep a reference to a String, there are some cases where it might be useful for it to do otherwise. For example, one could define one or more immutable "CompositeString" classes which would hold multiple strings, and have a ToString method which would concatenate them and cache the result. Using such types, it would be possible to make a loop like:

    for (i=0; i<100000; i++)
      st = st + something;
    

    yield performance that's almost within an order of magnitude of StringBuilder without having to make use of any observably-mutable class semantics (each iteration of the loop would generate a new CompositeString object, but a lot of information could be shared between objects).

    Even if initially one never stores anything other than a String into the data field, using Object and calling ToString() on it would make it possible to other implementations should the need arise.

    0 讨论(0)
  • 2021-01-11 10:44

    You are on the right track because you can create a value type (struct) to wrap a .NET primitive type and add some rules around the type without adding any real overhead.

    The only problem is that value types can be default initialized exactly as a string can be default initialized. So you cannot avoid that there exists an "invalid" or "empty" or "null" value.

    Here is a class that wraps a string with the added rule that the string cannot be null or empty. For lack of better name I decided to call it Text:

    struct Text : IEquatable<Text> {
    
      readonly String value;
    
      public Text(String value) {
        if (!IsValid(value))
          throw new ArgumentException("value");
        this.value = value;
      }
    
      public static implicit operator Text(String value) {
        return new Text(value);
      }
    
      public static implicit operator String(Text text) {
        return text.value;
      }
    
      public static Boolean operator ==(Text a, Text b) {
        return a.Equals(b);
      }
    
      public static Boolean operator !=(Text a, Text b) {
        return !(a == b);
      }
    
      public Boolean Equals(Text other) {
        return Equals(this.value, other.value);
      }
    
      public override Boolean Equals(Object obj) {
        if (obj == null || obj.GetType() != typeof(Text))
          return false;
        return Equals((Text) obj);
      }
    
      public override Int32 GetHashCode() {
        return this.value != null ? this.value.GetHashCode() : String.Empty.GetHashCode();
      }
    
      public override String ToString() {
        return this.value != null ? this.value : "N/A";
      }
    
      public static Boolean IsValid(String value) {
        return !String.IsNullOrEmpty(value);
      }
    
      public static readonly Text Empty = new Text();
    
    }
    

    You do not have to implement the IEquatable<T> interface but it is a nice addition because you have to override Equals anyway.

    I decided to create two implicit cast operators so this type can be used interchangeably with normal strings. However, implicit cast can be a bit subtle so you might decide to change one or both into explicit cast operators. If you decide to use implicit casts you should probably also override the == and != operator to avoid using the == operator for strings when you really want to use Equals for this type.

    You can use the class like this:

    var text1 = new Text("Alpha");
    Text text2 = "Beta"; // Implicit cast.
    var text3 = (Text) "Gamma"; // Explicit cast.
    var text4 = new Text(""); // Throws exception.
    
    var s1 = (String) text1; // Explicit cast.
    String s2 = text2; // Implicit cast.
    

    However, you still have a "null" or "empty" value:

    var empty = new Text();
    Console.WriteLine(Equals(text, Text.Empty)); // Prints "True".
    Console.WriteLine(Text.Empty); // Prints "N/A".
    

    This concept can easily be extended to more complex "strings", e.g. phone numbers or other strings with a structure. This will allow you to write code that is easier to understand. E.g., instead of

    public void AddCustomer(String name, String phone) { ... }
    

    you can change it to

    public void AddCustomer(String name, PhoneNumber phone) { ... }
    

    The second function does not need to validate the phone number because it already is a PhoneNumber that has to be valid. Compare that to a string that can have any content and in each call you have to validate it. Even though that most seasoned developers probably will agree that it is a bad practice to use strings for string like values like social security numbers, phone numbers, country codes, currencies etc. it seems to be a very common approach.

    Note that this approach does not have any overhead in terms of heap allocations. This is simply a string with some extra validation code.

    0 讨论(0)
提交回复
热议问题