C#创建一个不可为空的字符串。可能吗?不知何故?

时间:2014-10-24 18:25:28

标签: c# string non-nullable

所以你不能继承string。你不能制作一个不可为空的string。但我想这样做。我想要一个类,让我们称之为nString,否则返回默认值为null。我有JSON对象可能有谁知道有多少空字符串,甚至是空对象。我想创建具有永远不会返回null的字符串的结构。

public struct Struct
{
    public nString value;
    public nString value2;
}

我想我可以这样做:

public struct Struct
{
    public string val { get { return val ?? "N/A"; } set { val = value; } }
    public string val2 { get { return val2 ?? "N/A"; } set { val2 = value; } };
}

但那还有很多工作要做。有没有办法做到这一点?

8 个答案:

答案 0 :(得分:15)

你当然可以拥有以下nString结构:

public struct nString
{
    public nString(string value)
        : this()
    {
        Value = value ?? "N/A";
    }

    public string Value
    {
        get;
        private set;
    }

    public static implicit operator nString(string value)
    {
        return new nString(value);
    }

    public static implicit operator string(nString value)
    {
        return value.Value;
    }
}

...

public nString val 
{ 
    get;
    set;
}

obj.val = null;
string x = obj.val; // <-- x will become "N/A";

这将允许从string进行转换。在引擎盖下它执行与您的示例相同的演员表,您不必为每个房产输入它。我确实想知道这对你的应用程序的可维护性有什么作用。

答案 1 :(得分:3)

为了使我的nString结构完全正常运行,我向它添加了每个单独的字符串方法,包括重载。如果有人遇到这个问题,请随意复制粘贴此代码并坚持下去。我可能会在下一步添加文档。

/// <summary>
/// Non-nullable string.
/// </summary>
public struct nString
{
    public nString(string value)
        : this()
    {
        Value = value ?? "";
    }

    public nString(char[] value)
    {
        Value = new string(value) ?? "";
    }

    public nString(char c, int count)
    {
        Value = new string(c, count) ?? "";
    }

    public nString(char[] value, int startIndex, int length)
    {
        Value = new string(value, startIndex, length) ?? "";
    }

    public string Value
    {
        get;
        private set;
    }

    public static implicit operator nString(string value)
    {
        return new nString(value);
    }

    public static implicit operator string(nString value)
    {
        return value.Value ?? "";
    }

    public int CompareTo(string strB)
    {
        Value = Value ?? "";
        return Value.CompareTo(strB);
    }

    public bool Contains(string value)
    {
        Value = Value ?? "";
        return Value.Contains(value);
    }

    public void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count)
    {
        Value = Value ?? "";
        Value.CopyTo(sourceIndex, destination, destinationIndex, count);
    }

    public bool EndsWith(string value)
    {
        Value = Value ?? "";
        return Value.EndsWith(value);
    }

    public bool EndsWith(string value, StringComparison comparisonType)
    {
        Value = Value ?? "";
        return Value.EndsWith(value, comparisonType);
    }

    public override bool Equals(object obj)
    {
        Value = Value ?? "";
        return Value.Equals(obj);
    }

    public bool Equals(string value)
    {
        Value = Value ?? "";
        return Value.Equals(value);
    }

    public bool Equals(string value, StringComparison comparisonType)
    {
        Value = Value ?? "";
        return Value.Equals(value, comparisonType);
    }

    public override int GetHashCode()
    {
        Value = Value ?? "";
        return Value.GetHashCode();
    }

    public new Type GetType()
    {
        return typeof(string);
    }

    public int IndexOf(char value)
    {
        Value = Value ?? "";
        return Value.IndexOf(value);
    }

    public int IndexOf(string value)
    {
        Value = Value ?? "";
        return Value.IndexOf(value);
    }

    public int IndexOf(char value, int startIndex)
    {
        Value = Value ?? "";
        return Value.IndexOf(value, startIndex);
    }

    public int IndexOf(string value, int startIndex)
    {
        Value = Value ?? "";
        return Value.IndexOf(value, startIndex);
    }

    public int IndexOf(string value, StringComparison comparisonType)
    {
        Value = Value ?? "";
        return Value.IndexOf(value, comparisonType);
    }

    public int IndexOf(char value, int startIndex, int count)
    {
        Value = Value ?? "";
        return Value.IndexOf(value, startIndex, count);
    }

    public int IndexOf(string value, int startIndex, int count)
    {
        Value = Value ?? "";
        return Value.IndexOf(value, startIndex, count);
    }

    public int IndexOf(string value, int startIndex, StringComparison comparisonType)
    {
        Value = Value ?? "";
        return Value.IndexOf(value, startIndex, comparisonType);
    }

    public int IndexOf(string value, int startIndex, int count, StringComparison comparisonType)
    {
        Value = Value ?? "";
        return Value.IndexOf(value, startIndex, count, comparisonType);
    }

    public int IndexOfAny(char[] anyOf)
    {
        Value = Value ?? "";
        return Value.IndexOfAny(anyOf);
    }

    public int IndexOfAny(char[] anyOf, int startIndex)
    {
        Value = Value ?? "";
        return Value.IndexOfAny(anyOf, startIndex);
    }

    public int IndexOfAny(char[] anyOf, int startIndex, int count)
    {
        Value = Value ?? "";
        return Value.IndexOfAny(anyOf, startIndex, count);
    }

    public string Insert(int startIndex, string value)
    {
        Value = Value ?? "";
        return Value.Insert(startIndex, value);
    }

    public int LastIndexOf(char value)
    {
        Value = Value ?? "";
        return Value.LastIndexOf(value);
    }

    public int LastIndexOf(string value)
    {
        Value = Value ?? "";
        return Value.LastIndexOf(value);
    }

    public int LastIndexOf(char value, int startIndex)
    {
        Value = Value ?? "";
        return Value.LastIndexOf(value, startIndex);
    }

    public int LastIndexOf(string value, int startIndex)
    {
        Value = Value ?? "";
        return Value.LastIndexOf(value, startIndex);
    }

    public int LastIndexOf(string value, StringComparison comparisonType)
    {
        Value = Value ?? "";
        return Value.LastIndexOf(value, comparisonType);
    }

    public int LastIndexOf(char value, int startIndex, int count)
    {
        Value = Value ?? "";
        return Value.LastIndexOf(value, startIndex, count);
    }

    public int LastIndexOf(string value, int startIndex, int count)
    {
        Value = Value ?? "";
        return Value.LastIndexOf(value, startIndex, count);
    }

    public int LastIndexOf(string value, int startIndex, StringComparison comparisonType)
    {
        Value = Value ?? "";
        return Value.LastIndexOf(value, startIndex, comparisonType);
    }

    public int LastIndexOf(string value, int startIndex, int count, StringComparison comparisonType)
    {
        Value = Value ?? "";
        return Value.LastIndexOf(value, startIndex, count, comparisonType);
    }

    public int LastIndexOfAny(char[] anyOf)
    {
        Value = Value ?? "";
        return Value.LastIndexOfAny(anyOf);
    }

    public int LastIndexOfAny(char[] anyOf, int startIndex)
    {
        Value = Value ?? "";
        return Value.LastIndexOfAny(anyOf, startIndex);
    }

    public int LastIndexOfAny(char[] anyOf, int startIndex, int count)
    {
        Value = Value ?? "";
        return Value.LastIndexOfAny(anyOf, startIndex, count);
    }

    public int Length
    {
        get
        {
            Value = Value ?? "";
            return Value.Length;
        }
    }

    public string PadLeft(int totalWidth)
    {
        Value = Value ?? "";
        return Value.PadLeft(totalWidth);
    }

    public string PadLeft(int totalWidth, char paddingChar)
    {
        Value = Value ?? "";
        return Value.PadLeft(totalWidth, paddingChar);
    }

    public string PadRight(int totalWidth)
    {
        Value = Value ?? "";
        return Value.PadRight(totalWidth);
    }

    public string PadRight(int totalWidth, char paddingChar)
    {
        Value = Value ?? "";
        return Value.PadRight(totalWidth, paddingChar);
    }

    public string Remove(int startIndex)
    {
        Value = Value ?? "";
        return Value.Remove(startIndex);
    }

    public string Remove(int startIndex, int count)
    {
        Value = Value ?? "";
        return Value.Remove(startIndex, count);
    }

    public string Replace(char oldChar, char newChar)
    {
        Value = Value ?? "";
        return Value.Replace(oldChar, newChar);
    }

    public string Replace(string oldValue, string newValue)
    {
        Value = Value ?? "";
        return Value.Replace(oldValue, newValue);
    }

    public string[] Split(params char[] separator)
    {
        Value = Value ?? "";
        return Value.Split(separator);
    }

    public string[] Split(char[] separator, StringSplitOptions options)
    {
        Value = Value ?? "";
        return Value.Split(separator, options);
    }

    public string[] Split(string[] separator, StringSplitOptions options)
    {
        Value = Value ?? "";
        return Value.Split(separator, options);
    }

    public bool StartsWith(string value)
    {
        Value = Value ?? "";
        return Value.StartsWith(value);
    }

    public bool StartsWith(string value, StringComparison comparisonType)
    {
        Value = Value ?? "";
        return Value.StartsWith(value, comparisonType);
    }

    public string Substring(int startIndex)
    {
        Value = Value ?? "";
        return Value.Substring(startIndex);
    }

    public string Substring(int startIndex, int length)
    {
        Value = Value ?? "";
        return Value.Substring(startIndex, length);
    }

    public char[] ToCharArray()
    {
        Value = Value ?? "";
        return Value.ToCharArray();
    }

    public string ToLower()
    {
        Value = Value ?? "";
        return Value.ToLower();
    }

    public string ToLowerInvariant()
    {
        Value = Value ?? "";
        return Value.ToLowerInvariant();
    }

    public override string ToString()
    {
        Value = Value ?? "";
        return Value.ToString();
    }

    public string ToUpper()
    {
        Value = Value ?? "";
        return Value.ToUpper();
    }

    public string ToUpperInvariant()
    {
        Value = Value ?? "";
        return Value.ToUpperInvariant();
    }

    public string Trim()
    {
        Value = Value ?? "";
        return Value.Trim();
    }

    public string Trim(params char[] trimChars)
    {
        Value = Value ?? "";
        return Value.Trim(trimChars);
    }

    public string TrimEnd(params char[] trimChars)
    {
        Value = Value ?? "";
        return Value.TrimEnd(trimChars);
    }

    public string TrimStart(params char[] trimChars)
    {
        Value = Value ?? "";
        return Value.TrimStart(trimChars);
    }
}

答案 2 :(得分:2)

您走在正确的轨道上是因为您可以创建一个值类型(struct)来包装.NET基元类型并在类型周围添加一些规则而不会增加任何实际开销。

唯一的问题是值类型可以默认初始化,因为字符串可以默认初始化。所以你无法避免存在&#34;无效&#34;或&#34;空&#34;或&#34; null&#34;值。

这是一个使用添加的规则包装字符串的类,该字符串不能为null或为空。由于缺乏更好的名字,我决定称之为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();

}

您不必实现IEquatable<T>界面,但这是一个很好的补充,因为您无论如何都必须覆盖Equals

我决定创建两个隐式转换运算符,因此这种类型可以与普通字符串互换使用。但是,隐式转换可能有点微妙,因此您可能决定将其中一个或两个更改为显式转换运算符。如果您决定使用隐式强制转换,您可能还应覆盖==!=运算符,以避免在您真正想要==使用Equals字符串时使用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. 运算符类型。

你可以使用这样的类:

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

但是,你仍然有一个&#34; null&#34;或&#34;空&#34;值:

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

这个概念可以很容易地扩展到更复杂的字符串&#34;电话号码或其他带结构的字符串。这将允许您编写更易于理解的代码。例如,而不是

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

您可以将其更改为

PhoneNumber

第二个功能不需要验证电话号码,因为它已经是{{1}}必须有效。将其与可以包含任何内容的字符串进行比较,并且在每次调用中都必须对其进行验证。尽管大多数经验丰富的开发人员可能会同意将字符串用于像社会安全号码,电话号码,国家代码,货币等字符串这样的字符串是一种不好的做法,但这似乎是一种非常常见的做法。

请注意,此方法在堆分配方面没有任何开销。这只是一个带有一些额外验证码的字符串。

答案 3 :(得分:1)

您可以使用类似extension method

的内容
public static class StringExtensions
{
    public static string GetValueOrNotAvailable(this string value)
    {
        return value ?? "N/A";
    }
}

那么你就可以这样称呼它

string s = (some variable that could be null)
Console.WriteLine(s.GetValueOrNotAvailable());

遗憾的是你不能覆盖string的get方法,你可以创建一个新的类型来跟踪你上面的内部字符串。

答案 4 :(得分:1)

可以定义一个“不可变”(*)struct,其行为几乎与String完全相同,但其默认值的行为类似于空字符串而不是null。这样的类型应封装StringObject类型的单个字段,定义从String缩小转换,以确保提供的字符串为非null并将其存储在其数据字段中,以及向String的扩展转换,如果该字段为空,则返回空字符串,否则返回其ToString()值。对于String的每个公共成员,该类型应定义一个调用(String)this的相应成员的成员。这种类型还应该为字符串连接运算符定义重载。

(*)所有可以保存与默认值明显不同的值的值类型都是可变的,因为struct1 = struct2;将通过覆盖所有公共和私有字段来改变存储在struct1中的实例。 type2中相应字段的内容,并且结构类型无法阻止它。

虽然在大多数情况下,人们希望拥有这样一种类型,只需保留对String的引用,但在某些情况下,它可能对它有用。例如,可以定义一个或多个不可变的“CompositeString”类,它们将包含多个字符串,并且具有ToString方法,该方法将连接它们并缓存结果。使用这样的类型,可以创建一个循环:

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

产生的性能几乎在StringBuilder的一个数量级内,而不必使用任何可观察的可变类语义(循环的每次迭代都会生成一个新的CompositeString对象,但很多可以在对象之间共享信息。

即使最初从未将String以外的任何内容存储到数据字段中,使用Object并在其上调用ToString()也可以在需要时使其他实现成为可能。

答案 5 :(得分:0)

不,你不能这样做。

创建非可空类型的唯一方法是声明struct - 结构,但不能继承或继承。

按原样使用属性很可能是最好的方法,或者如前所述在反序列化期间进行空合并,但C#只是为了处理null值而设计的。

答案 6 :(得分:0)

随着C#8于2019年4月和nullable reference types的发布,此功能现已成为语言功能。

答案 7 :(得分:0)

出于好奇,我想知道这样的事情并遇到了这个问题。其他答案似乎没有考虑到 .git/packed-refsnString myString = new nString(); 的情况。

在这些情况下,nString myString = default 将等于 null,因为结构始终具有无参数构造函数。这意味着在上述情况下,构造函数中接受字符串并替换任何空值的代码永远不会被调用。

这是一个快速更新的版本,可确保 myString.Value 属性永远不会返回空值。这使用 C# 8 的可为空引用类型和 C# 9 的较短的“new()”表达式,因此如果您不是最新的,则可能需要稍微更改它。

Value