在依赖注入中检查null的更好方法

时间:2014-10-13 06:58:46

标签: c# asp.net dependency-injection

通过构造函数使用依赖项注入时,我总是需要在将实例传递给内部属性之前检查空值。 e.g。

public UserManager(User user, IStateManager stateManager)
{
    if(user == null) throw new arguementexception("user");
    if(statemanager == null) throw new arguementexception("stateManager");

    _user = user;
    _stateManager = statemanager;
} 

在每个控制器/类上重复此模式似乎是重复的。有没有更好的方法来处理这个?顺便说一句不同的控制器将有不同的构造函数初始化程序。我正在使用Simple Injector进行DI。

3 个答案:

答案 0 :(得分:12)

这是重复的代码,但它几乎不会成为一个问题,因为这会导致代码库中的彻底改变吗?您是否需要更改许多支票?几乎不。请查看更详细的this blog post

老实说,当涉及到我的injection constructors时,我几乎再也不会添加那些空检查,因为我知道我的DI容器在自动连接这些类型时不会将空引用注入我的构造函数中。这使我免于编写所有这些空检查。

有些人可能会说我现在用我的DI容器编写代码,但我会反对。我只是编写了解决我问题所需的最少量代码。在我的情况下,添加这些空检查对我没有帮助。

但请注意,如果我正在为可重用的库编写代码,我绝对会编写那些空检查,因为我不知道是谁在调用该代码。对于不用作注入构造函数(消息,实体,值类型,DTO)的构造函数,我实际上要添加这些检查。但是这里有一些想法如何使这更好一点:

你可以像这样添加一个很好的帮助方法:

public UserManager(User user, IStateManager stateManager)
{
    Requires.IsNotNull(user, "user");
    Requires.IsNotNull(statemanager, "statemanager");

    _user = user;
    _stateManager = statemanager;
}

然而,这并没有真正帮助减少重复代码,尽管它确实减少了生成的机器代码的实际大小(但这几乎不是问题)。因此,您可以使此方法返回如下值:

public UserManager(User user, IStateManager stateManager)
{
    _user = Requires.IsNotNull(user, "user");
    _stateManager = Requires.IsNotNull(statemanager, "statemanager");
}

或者......使用C#6.0:

public UserManager(User user, IStateManager stateManager)
{
    _user = Requires.IsNotNull(user, nameof(user));
    _stateManager = Requires.IsNotNull(statemanager, nameof(statemanager));
}

您可以按如下方式实施此方法:

public static class Requires {
    public static T IsNotNull<T>(T instance, string paramName) where T : class {
        // Use ReferenceEquals in case T overrides equals.
        if (object.ReferenceEquals(null, instance) {
            // Call a method that throws instead of throwing directly. This allows
            // this IsNotNull method to be inlined.
            ThrowArgumentNullException(paramName);
        }

        return instance;
    }

    private static void ThrowArgumentNullException(paramName) {
        throw new ArgumentNullException(paramName);
    }
}

我们只希望C#8添加non-nullable reference types。这将允许我们将代码减少到以下内容:

public UserManager(User! user, IStateManager! stateManager)
{
    _user = user;
    _stateManager = statemanager;
}

如果C#在定义类型方面有更好的方法(这是C#6的提案,但从未添加到最终版本中),以下内容可能会更简化类型定义:

public class UserManager(User! user, IStateManager! stateManager)
{
}

答案 1 :(得分:1)

我使用静态方法.ThrowIfNull,如果为null,将抛出具有正确参数名称的ArgumentNullException。

public MyClass 
{
  public MyClass(DependencyType1 firstDependency, DependencyType2 secondDependency)
  {
     Arguments.ThrowIfNull(firstDependency, secondDependency);

     _firstDependency = firstDependency;
     _secondDependency = secondDependency;
  }
}

public static class Arguments
    {
        public static void ThrowIfNull(params object[] args)
        {
            for (var i = 0; i < args.Length; i++)
            {
                if (args[i] != null
                    && args[i].GetType() == typeof(ArgumentAction)
                    && (ArgumentAction)args[i] == ArgumentAction.Skip) continue;
                if (args[i] == null) throw GetArgumentNullException(i);
            }
        }

        private static ArgumentNullException GetArgumentNullException(int argIndex)
        {
            var frame = new StackFrame(2, false);
            var method = frame.GetMethod();
            var args = method.GetParameters();
            var name = args[argIndex].Name;
            return new ArgumentNullException(name);
        }

        public enum ArgumentAction
        {
            Undefined = 0,
            Skip
        }
    }

答案 2 :(得分:1)

事实上,此类检查会产生0业务价值,但会在代码中产生额外的噪音。同事开发人员会感到困惑,他们将不得不花费额外的努力来了解你为什么要进行所有这些检查。 通常,当无法解析依赖关系时,IoC容器将抛出错误。即使你的容器没有抛出,你也会在执行期间稍后获得null引用异常。最好添加Debug.Assert语句,这表明你不确定这里到底发生了什么,但有时候你有空