【问题标题】:Better way of checking null in dependency injection在依赖注入中检查 null 的更好方法
【发布时间】:2021-11-06 16:27:41
【问题描述】:

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

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

    _user = user;
    _stateManager = statemanager;
} 

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

【问题讨论】:

  • 只是作为一个侧节点,你在这里传递的是具体的对象,还是接口?我的印象是 DI 会注入接口的具体实现(在构造函数中,在你的情况下)。也许我错过了什么。
  • 嗨,我更正了。 StateManager 将传递一个接口。大多数构造函数将使用接口,但在这个小场景中,用户将是部分的。

标签: c# asp.net dependency-injection


【解决方案1】:

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

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

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

但请注意,如果我正在为可重用库编写代码,我绝对会编写那些 null 检查,因为我不知道是谁在调用该代码。对于不用作注入构造函数的构造函数(消息、实体、值类型、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# 9 改变。有一个建议使用 ! 符号添加运行时检查:

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

【讨论】:

  • 非常干净的方式。谢谢。
【解决方案2】:

我使用静态方法 .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
        }
    }

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      如果您真的坚持检查注入的依赖项,在 .NET 6 中这里是一个 new method in .NET API ArgumentNullException.ThrowIfNull(someParameter)

      我的建议是正确配置您的 DI 或使用 PostSharp。

      【讨论】:

        猜你喜欢
        • 2020-12-12
        • 2021-01-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多