正如 Daniel Hilgarth 指出的那样,没有万无一失的方法可以实现这一目标。我的建议与他的类似,但增加了一些安全性。您可以自己决定在整个程序中使用包装器类型的好处是否超过成本。
struct NonNull<T> where T : class {
private readonly T _value;
private readonly bool _isSafe;
public NonNull(T value) {
if (value == null)
throw new ArgumentNullException();
_value = value;
_isSafe = true;
}
public T Value {
get {
if (_isSafe) return _value;
throw new ArgumentNullException();
}
}
public static implicit operator T(NonNull<T> nonNull) {
return nonNull.Value;
}
}
static class NonNull {
public static NonNull<T> Create<T>(T value) where T : class {
return new NonNull<T>(value);
}
}
包装器类型主要是为了使您的意图自我记录,因此您不太可能使用零初始化结构绕过它,但它保留一个标志以指示它无论如何都已正确初始化。在那种公认的不寻常的情况下,它会ArgumentNullException
在访问该值时抛出一个。
class Program {
static void Main(string[] args) {
IsEmptyString(NonNull.Create("abc")); //false
IsEmptyString(NonNull.Create("")); //true
IsEmptyString(null); //won't compile
IsEmptyString(NonNull.Create<string>(null)); //ArgumentNullException
IsEmptyString(new NonNull<string>()); //bypassing, still ArgumentNullException
}
static bool IsEmptyString(NonNull<string> s) {
return StringComparer.Ordinal.Equals(s, "");
}
}
现在,这比偶尔的 NRE 更好吗?也许。它可以节省大量样板 arg 检查。您需要确定它是否适合您的情况。缺少编译器支持,就像 F# 提供的那样,没有办法提供编译时空安全性,但您可以(可以说)简化运行时安全性。
您可能希望在 Microsoft 的客户反馈网站上对此问题进行投票:在 C# 中添加不可为空的引用类型