【问题标题】:Enforcing method don't return null强制方法不返回 null
【发布时间】:2018-01-10 08:39:06
【问题描述】:

正如 8 年前Here 提出的问题,但我认为应该有一种方法(新模式、新设计、新架构或其他任何东西。)强制方法不返回 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 ,如果方法的返回类型是结构,那么它们就不能返回 null 并且我们在编译时知道这一点 不在运行时。

所以我实现了上面的方法:

1- 为方法创建 DTO outin,在这种情况下只需 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 是最好的方法吗?是否值得更改所有方法并为每个方法创建 DTO inout(我认为是这样)。

有没有更好的方法来做到这一点,任何想法、帮助、答案都将不胜感激。

【问题讨论】:

  • @Aria 方法不会返回 null 因为它返回的是一个结构,但它仍然返回一个带有空引用的结构,如果您检查 IsEmpty 那么您有相同的代码(在调用点),如果你不这样做,那么你有完全相同的异常(在调用点)。您只需支付一些开销(并为原始代码添加一些晦涩难懂的内容)。如果这是不能发生的事情,那么我同意帕特里克的观点:代码合同(或者,如果你的目标是核心,至少是断言)。
  • “如您所知,在方法中返回 null 存在一些问题” - 会是什么?肯定有“暗示” - 但问题 ?
  • 所以你用更糟糕的方法来规避它? @阿里亚
  • @Aria 请注意,如果null 永远不允许(并且您不能引入合同),那么您应该在假设的NotNull<T> 结构中进行一些检查(至少在值为是创建的,而不是在使用时创建的(可能会在以后以不明显的方式进行优化)。
  • @Fildor 已编辑的问题已更改为含义,谢谢我的英语仍然很弱。

标签: c# oop null nullreferenceexception


【解决方案1】:

您的“引用类型”到“带有属性检查的结构”的转换对我来说似乎没用。它还需要对您的意图有深入的了解,而引用类型 null 检查对于以后阅读它的任何人来说都是非常明显的。

我认为code contracts 可以为您工作。它为您提供编译时静态分析和运行时检查。只需确保您有适当的合同作为后期条件:

public ClassName Do()
{
    ...

    object returnValue = null;

    Contract.Ensures(returnValue != null);

    return returnValue;
}

【讨论】:

  • 那么Contract.Ensures是我想要的(编译时间),如果消费者和服务在不同的层,我的意思是我不希望服务的操作返回null
  • 你不能在那里使用相同的方法吗?
  • 如果我理解你,不,因为将来消费者可能是外部产品,而不是我们的产品。
  • @Aria 是服务还是消费者可能是第 3 方?
  • 那我就不明白了。如果您确保在服务中返回的项目永远不会为空,那么只需在接口描述中声明即可。如果在必须返回 null 的情况下是错误/失败,则异常/错误结果是正确的方法。当然,这也必须在文档中说明。
【解决方案2】:

假设该值永远不会是null,否则if 是不可避免的(但对于单个方法调用,您现在可以写Do()?.DoSomething())。

如果您可以引入代码合同(请参阅Patrick's answer),那么我完全同意 Patrick 的观点,您应该接受他们。如果它不可行(因为您的代码库已经太大或者您的目标环境不支持它们),那么我会首先使用断言:

var obj = Do();
Debug.Assert(obj != null);

// Use obj...

但是,我们正在将此责任转移到调用点,这可能很乏味。如果你想让这个接口显式,那么你可以按照你的想法使用结构体,但在调用点(错误所在)抛出异常:

public NotNullable<SomeClass> Do() { }

其中NotNullable&lt;T&gt;定义为:

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() 的行为和最小(但仍然存在)开销的发布版本。

请注意,仅当您希望document 接口方法直接在其签名中有关此约束时才有意义。如果您对此不感兴趣,请使其尽可能简单:

public SomeClass Do()
{
    MyClass somevalue = ...

    // ...

    return NotNull(somevalue);
}

其中NotNull() 是在某处定义并使用using static 导入的静态方法,甚至是object 的扩展方法return somevalue.NotNull()

我不是特别喜欢这种方法,因为我认为Debug.Assert() 在这些情况下就足够了,但这只是我的看法。当然,也许有一天我们会在 C# 中使用 Nullable Reference Types,然后我们将获得编译时强制(如 object? 或相反的 object!)。

【讨论】:

  • 为什么不直接调用验证方法而不是创建结构体?
  • @AdrianoRepetti 感谢您的努力和关注,但对我来说最大的好处是编译时并且不要让方法返回 null。
  • @Aria 我想是这样,您可以添加的唯一编译时要求是合同(但它们不会跨越 WCF 服务边界)。现在他们是你能做的最好的(AFAIK)
【解决方案3】:

返回 null 是一种不好的做法 - 更好地实施 NullObject Design Pattern

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多