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

本文关键字:何故 创建 字符串 | 更新日期: 2023-09-27 18:27:54

所以你不能继承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; } };
}

但工作量要大得多。有什么办法可以做到这一点吗?

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

当然,您可以有以下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和到进行转换。在后台,它执行与您的示例相同的转换,您只是不必为每个属性键入它。不过,我确实想知道这对您的应用程序的可维护性有什么影响。

为了使我的 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);
    }
}

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

唯一的问题是值类型可以默认初始化,就像字符串可以默认初始化一样。因此,您无法避免存在"无效"或"空"或"空"值。

下面是一个类,它使用添加的规则包装字符串,该规则(字符串不能为 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".

这个概念可以很容易地扩展到更复杂的"字符串",例如电话号码或其他具有结构的字符串。这将允许您编写更易于理解的代码。例如,代替

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

您可以将其更改为

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

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

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

随着 2019 年 4 月发布的 C# 8 和可为空的引用类型,这现在是一项语言功能。

您可以使用类似扩展方法的内容

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 方法,您可以创建一个跟踪内部字符串的新类型,如上面所示。

可以

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

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

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

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

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

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

不,你不能这样做。

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

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

出于

好奇,我想知道这样的事情,遇到了这个问题。其他答案似乎没有考虑到nString myString = new nString();nString myString = default的情况。

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

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

public readonly struct nString
{
    private readonly string? _value;
    
    public string Value => _value ?? string.Empty; // Replace string.Empty with any other desired default value.
    public nString(string? value)
    {
        _value = value;
    }
    public static implicit operator nString(string? value) => new(value);
    public static implicit operator string(nString value) => value.Value;
}