23

所以不能继承string。你不能制作一个不可为空的string。但我想这样做。我想要一个类,我们称它为 nString,它返回一个默认值,否则它会为空。我有可能知道有多少空字符串甚至空对象的 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; } };
}

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

4

8 回答 8

23

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

于 2014-10-24T18:38:30.763 回答
4

为了使我的 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);
    }
}
于 2014-10-24T21:09:26.593 回答
2

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

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

这是一个包装字符串的类,其中添加了字符串不能为空或为空的规则。由于没有更好的名字,我决定称它为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无论如何您都必须覆盖。

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

但是,您仍然有一个“null”或“empty”值:

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必须有效的。将其与可以包含任何内容的字符串进行比较,并且在每次调用中都必须对其进行验证。尽管大多数经验丰富的开发人员可能会同意,将字符串用于诸如社会保险号、电话号码、国家代码、货币等字符串之类的值是一种不好的做法。这似乎是一种非常常见的方法。

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

于 2014-10-24T19:05:07.763 回答
1

你可以使用类似扩展方法的东西

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

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

于 2014-10-24T18:35:09.977 回答
1

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

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

尽管在大多数情况下,人们希望这样一种类型只保留对 a 的引用String,但在某些情况下,它可能对它有用。例如,可以定义一个或多个不可变的“CompositeString”类,这些类将保存多个字符串,并具有一种ToString将它们连接起来并缓存结果的方法。使用这样的类型,就可以进行如下循环:

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

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

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

于 2014-10-24T19:02:55.803 回答
1

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

于 2019-07-05T08:03:23.190 回答
0

不,你不能这样做。

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

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

于 2014-10-24T18:39:02.217 回答
0

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

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

这是一个快速更新的版本,可确保该Value属性永远不会返回空值。这使用了 C# 8 的可为空引用类型和 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;
}
于 2021-01-06T08:05:06.040 回答