正如 8 年前在这里提出的问题,但我认为应该有一种方法(新模式、新设计、新架构或其他任何东西)来强制执行方法不返回 null。
如您所知,在对我来说很重要的方法中返回 null 有一些含义:
在消费端和可理解语义中处理 null,例如:
方法:
public ClassName Do()
{
...
return null;
}
并称Do()
为(注意评论也):
var objVal = Do();
//Accessing property of ClassName raised exception
var pnVal = objVal.PropName;//Exception id objVal is null
//But I should handle if it is not null then do anything I want
if(objVal!= null)
{
//Do something
}
在通过上述方式对产品产生许多问题之后,我得出了这个结论,即概括所有方法,以遵循可读,干净并防止歧义语义的模式。
所以一个非常基本的方法是使用Struct
类型,因为结构不能为空,如果方法的返回类型是结构的,那么它们不能返回 null,我们知道这是在编译时而不是在运行时
。所以我实现了上面的方法,例如:
1-使DTOout
并in
方法,在这种情况下只需out
:
public struct Do_DTO_Out
{
public ClassName Prop1 { get; set; }
public bool IsEmpty
{
get
{
return Prop1 == null;
}
}
public static Do_DTO_Out Empty
{
get
{
return new Do_DTO_Out() { Prop1 = null };
}
}
}
2-Do
方法应为:
public Do_DTO_Out Do()
{
try
{
return manipulatedObj;
}
catch (Exception exp)
{
}
return Do_DTO_Out.Empty;
}
3-在消费方面:
var objVal = Do();
if (!objVal.IsEmpty)
//Do something
struct
是最好的方法吗? 是否值得更改所有方法并为每个方法创建 DTOin
和out
(我认为是这样)。
有没有更好的方法可以做到这一点,任何想法,帮助,答案将不胜感激。
您的"引用类型"到"带有属性检查的结构"的转换对我来说似乎毫无用处。它还需要深入了解您的意图,而引用类型 null 检查对于以后阅读它的任何人来说都非常明显。
我认为代码合同可以为您工作。它为您提供编译时静态分析和运行时检查。只要确保您有适当的合同作为发布条件:
public ClassName Do()
{
...
object returnValue = null;
Contract.Ensures(returnValue != null);
return returnValue;
}
假设该值永远无法null
否则if
是不可避免的(但对于单个方法调用,您现在可以编写Do()?.DoSomething()
)。
如果你可以引入代码契约(参见 Patrick 的回答),那么我完全同意 Patrick 的观点,你应该接受它们。如果它不可行(因为你的代码库已经太大了,或者你的目标是一个不支持它们的环境),那么我会首先使用断言:
var obj = Do();
Debug.Assert(obj != null);
// Use obj...
但是,我们将此责任转移到呼叫点,这可能很乏味。如果你想使这个接口显式,那么你可以按照你的想法使用一些结构体,但在调用点(错误所在)抛出异常:
public NotNullable<SomeClass> Do() { }
其中NotNullable<T>
定义为:
public struct NotNullable<T> where T : class
{
public NotNullable(T value)
{
Value = value ?? throw new ArgumentNullException(nameof(value));
}
public T Value { get; }
}
但是我不喜欢在调用点显式访问.Value
,然后我会使其透明,并添加:
public static implicit operator T(NotNullable<T> rhs)
=> rhs.Value;
现在调用者可以是:
MyClass obj = Do();
obj.DoSomthing();
创建对象时会抛出正确的异常(不幸的是,在运行时)。使用[Conditional("DEBUG")]
,您可以排除对具有类似于Debug.Assert()
的行为和最小(但仍然存在)开销的发布版本的检查。
请注意,仅当您要直接在其签名中记录有关此约束的接口方法时,这才有意义。如果您对此不感兴趣,请尽可能保持简单:
public SomeClass Do()
{
MyClass somevalue = ...
// ...
return NotNull(somevalue);
}
其中NotNull()
是一个静态方法,在某处定义并使用using static
导入,甚至是object
的扩展方法,称为return somevalue.NotNull()
。
我不是特别喜欢这种方法,因为我认为在这些情况下Debug.Assert()
就足够了,但这只是我的意见。当然,也许有一天我们会在 C# 中拥有可为空的引用类型,然后我们将获得编译时强制(如object?
或相反的方式object!
)。
返回 null 是一种不好的做法 - 最好实现空对象设计模式