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

发布于 2024-08-14 00:59:39 字数 694 浏览 5 评论 0原文

我有以下扩展方法:

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 的值。

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

I have the following extension method:

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

and this is an example of its usage....

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

works 100% fine.

But, I don't like how I have to provide the name of the variable, just to help my exception message.

I was wondering if it's possible to refactor the extension method, so it could be called like this...

qwerty.ThrowIfArgumentIsNull();

and it automatically figures out that the name of the variable is 'qwerty' and therefore uses that as the value for the ArgumentNullException.

Possible? I'm assuming reflection could do this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

七分※倦醒 2024-08-21 00:59:40

“这个扩展方法可以重构一下吗?”

正如其他人已经说过的,如果不使用一些涉及的 ​​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 .分钟/

"Is it possible to refactor this extension method?"

As others already said, there's not much you can do without using some AOP involved (like package NullGuard.Fody), but you can spice up your version a little bit to make it more flexible:

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 */ 
quot;My custom error text");
    }
}

I am using many more Methods for various types, like Enumerables, Strings, etc.

You can find the Sourcecode of an older version here:
https://github.com/CleanCodeX/Common.Shared.Min

or simply use the Nuget package, which will be updated from time to time so you only have to update the package if you want to.
https://www.nuget.org/packages/CCX.Common.Shared.Min/

葬心 2024-08-21 00:59:39

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

Contract.Requires(qwerty != null);

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

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

编辑:要扩展劳伦特的答案,您可能有:

new { qwerty }.CheckNotNull();

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

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

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

编辑:代码实现,以及 博客文章正式制作。恶心,但很有趣。

No, you can't do this. It would be nice, but it's not possible without some sort of AOP getting involved. I'm sure PostSharp can do a nice job, hopefully using attributes, and in Code Contracts it would just be:

Contract.Requires(qwerty != null);

Ideally I'd like a PostSharp attribute which generates the Code Contracts call - and I'll play around with that at some point - but until then, the extension method you've got is the best approach I've found...

(If I ever try the PostSharp + Code Contracts approach, I'll certainly blog about it, btw... Mono Cecil might make it reasonably easy too.)

EDIT: To expand on Laurent's answer, you could potentially have:

new { qwerty }.CheckNotNull();

And if you had lots of non-nullable parameters, you could have:

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

This would have to use reflection to work out the properties. There are ways that you could avoid doing the reflection on every access, building a delegate for each property and generally making it whizzy. I may investigate this for a blog post... but it's somewhat icky, and I prefer the idea of being able to just attribute the parameters...

EDIT: Code implemented, and blog post duly made. Ick, but fun.

花想c 2024-08-21 00:59:39

一句话:不。

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

In a word: no.

The extension method is passed a value. It has no idea where the value comes from or what identifier the caller may have choosen to refer to it as.

旧时浪漫 2024-08-21 00:59:39

我发现使用代码片段最容易做到这一点。

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

这是片段:

<?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>

I find it easiest to do this using a code snippet.

In your example, I can type tna<tab>qwerty<enter>.

Here is the snippet:

<?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>
再浓的妆也掩不了殇 2024-08-21 00:59:39

另请参阅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
   }
}

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

See also ArgumentNullException and
refactoring
for a complete
solutions along the same lines as the
answer.

What about:

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

then define ThrowIfArgumentIsNull as

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

sorry I don't have the time to fill in the detail or provide the full code at present.

复古式 2024-08-21 00:59:39

我建议您宁愿执行以下操作:

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

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

I would recommend that you rather do the following:

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

Using generics in this case doesn't seem to add any value. But as to your original question, I don't think that's possible.

余生再见 2024-08-21 00:59:39

我喜欢强制 Lokad 共享库

基本语法:

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

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

I like Enforce from the Lokad Shared Libraries.

Basic syntax:

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

This will throw an exception with the parameter name and type if any of the arguments is null.

轻拂→两袖风尘 2024-08-21 00:59:39

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

ArgumentNullException.ThrowIfNull(argument);

As of .NET 6, you can use the following:

ArgumentNullException.ThrowIfNull(argument);

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文