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; } };
}
但工作量要大得多。有什么办法可以做到这一点吗?
当然,您可以有以下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
。 这样的类型应该封装类型为 String
或 Object
的单个字段,定义从 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;
}