这个扩展方法可以重构吗?

问题描述 投票:0回答:8

我有以下扩展方法:

public static void ThrowIfArgumentIsNull<T>(this T value, string argument) 
    where T : class
{
    if (value == null)
    {
        throw new ArgumentNullException(argument);
    }
}

这是其用法的一个示例......

// Note: I've poorly named the argument, on purpose, for this question.
public void Save(Category qwerty)
{
    qwerty.ThrowIfArgumentIsNull("qwerty");
    ....
}

100% 正常工作。

但是,我不喜欢必须提供变量名称,只是为了帮助我的异常消息。

我想知道是否可以重构扩展方法,这样就可以这样调用......

qwerty.ThrowIfArgumentIsNull();

它会自动找出变量的名称是“qwerty”,因此将其用作 ArgumentNullException 的值。

可能吗?我假设反射可以做到这一点?

.net extension-methods
8个回答
35
投票

不,你不能这样做。这固然很好,但如果没有某种 AOP 的参与,这是不可能的。我确信 PostSharp 可以做得很好,希望使用属性,并且在代码契约中它只是:

Contract.Requires(qwerty != null);

理想情况下,我想要一个生成代码契约调用的 PostSharp 属性 - 我会在某个时候使用它 - 但在那之前,您所拥有的扩展方法是我发现的最佳方法......

(如果我尝试过 PostSharp + 代码契约方法,我肯定会在博客上介绍它,顺便说一句...Mono Cecil 也可能使它变得相当简单。)

编辑:为了扩展洛朗的答案,你可能有:

new { qwerty }.CheckNotNull();

如果您有很多不可为空的参数,您可以:

new { qwerty, uiop, asdfg }.CheckNotNull();

这必须使用反射来计算属性。有一些方法可以避免在每次访问时进行反射,为每个属性构建一个委托,并通常使其变得清晰。我可能会在博客文章中对此进行调查...但这有点令人讨厌,而且我更喜欢能够仅归因于参数的想法...

编辑:代码已实施,并且博客文章已正式制作。恶心,但很有趣。


3
投票

一句话:不。

扩展方法传递一个值。它不知道该值来自哪里,也不知道调用者可能选择将其引用为什么标识符。


2
投票

我发现使用代码片段最简单。

在您的示例中,我可以输入

tna<tab>qwerty<enter>

这是片段:

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
                <Title>Check for null arguments</Title>
                <Shortcut>tna</Shortcut>
                <Description>Code snippet for throw new ArgumentNullException</Description>
                <Author>SLaks</Author>
                <SnippetTypes>
                        <SnippetType>Expansion</SnippetType>
                        <SnippetType>SurroundsWith</SnippetType>
                </SnippetTypes>
        </Header>
        <Snippet>
                <Declarations>
                        <Literal>
                                <ID>Parameter</ID>
                                <ToolTip>Paremeter to check for null</ToolTip>
                                <Default>value</Default>
                        </Literal>
                </Declarations>
                <Code Language="csharp"><![CDATA[if ($Parameter$ == null) throw new ArgumentNullException("$Parameter$");
        $end$]]>
                </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>

2
投票

另请参阅 ArgumentNullException 和 重构以获得完整的 解决方案与 回答。

关于:

public void Save(Category qwerty)
{   
   ThrowIfArgumentIsNull( () => qwerty );
   qwerty.ThrowIfArgumentIsNull("qwerty");    
   // ....
}

然后将 ThrowIfArgumentIsNull 定义为

public static void ThrowIfArgumentIsNull(Expression<Func<object>> test)
{
   if (test.Compile()() == null)
   {
      // take the expression apart to find the name of the argument
   }
}

抱歉,我目前没有时间填写详细信息或提供完整的代码。


1
投票

我建议您最好执行以下操作:

public static void ThrowIfArgumentIsNull(this object value, string argument) 
{
    if (value == null)
    {
        throw new ArgumentNullException(argument);
    }
}

在这种情况下使用泛型似乎没有增加任何价值。但至于你原来的问题,我认为这是不可能的。


1
投票

我喜欢来自 Lokad 共享库Enforce

基本语法:

Enforce.Arguments(() => controller, () => viewManager,() => workspace);

如果任何参数为空,这将引发参数名称和类型的异常。


1
投票

从 .NET 6 开始,您可以使用以下

ArgumentNullException.ThrowIfNull(argument);


0
投票

“是否可以重构这个扩展方法?”

正如其他人已经说过的,如果不使用一些涉及的 AOP(例如包 NullGuard.Fody),你就无能为力,但是可以为你的版本增添一点趣味,使其更加灵活: public static class Requires { public static T NotNull<T>([NotNull] T? arg, string argName, string? customErrorText = null) { if (arg is null) throw new ArgumentNullException(argName, customErrorText ?? Strings.ArgumentNull(argName)); return arg; } // For all types public static T NotDefault<T>(T arg, string argName, string? customErrorText = null) { if (EqualityComparer<T>.Default.Equals(arg, default!)) throw new ArgumentException(customErrorText ?? Strings.ArgumentHasTypeDefault(argName), argName); return arg; } } // Extensions public static class GenericTypeParamCheckingExtensions { [return: NotNull] public static T NotNull<T>([NotNull] this T? source, string argName, string? customErrorText = null) where T : class => source ?? throw ExceptionsHelper.ArgumentNull(argName, customErrorText); // For all types public static T NotDefault<T>(this T source, string argName, string? customErrorText = null) { if (EqualityComparer<T>.Default.Equals(source, default)) throw ExceptionsHelper.ArgumentDefault(argName, customErrorText); return source; } } // Usage public class YourClass { private void YourMethod(string? nullableParam, int nonNullableParam) { // option 1 - just param checking nullableParam.NotNull(nameof(nullableParam)); nonNullableParam.NotDefault(nameof(nonNullableParam)); // option 2 - param checking and value retrieval if no exception occurred var stringValue = nullableParam .NotNull(nameof(nullableParam)); var intValue = nonNullableParam .NotDefault(nameof(nonNullableParam), /* optional */ $"My custom error text"); } }

我正在对各种类型使用更多方法,例如枚举、字符串等。

您可以在这里找到旧版本的源代码:

https://github.com/CleanCodeX/Common.Shared.Min

或者直接使用 Nuget 包,该包会不时更新,因此您只需在需要时更新包即可。

https://www.nuget.org/packages/CCX.Common.Shared.Min/

© www.soinside.com 2019 - 2024. All rights reserved.