我有以下扩展方法:
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 的值。
可能吗?我假设反射可以做到这一点?
不,你不能这样做。这固然很好,但如果没有某种 AOP 的参与,这是不可能的。我确信 PostSharp 可以做得很好,希望使用属性,并且在代码契约中它只是:
Contract.Requires(qwerty != null);
理想情况下,我想要一个生成代码契约调用的 PostSharp 属性 - 我会在某个时候使用它 - 但在那之前,您所拥有的扩展方法是我发现的最佳方法......
(如果我尝试过 PostSharp + 代码契约方法,我肯定会在博客上介绍它,顺便说一句...Mono Cecil 也可能使它变得相当简单。)
编辑:为了扩展洛朗的答案,你可能有:
new { qwerty }.CheckNotNull();
如果您有很多不可为空的参数,您可以:
new { qwerty, uiop, asdfg }.CheckNotNull();
这必须使用反射来计算属性。有一些方法可以避免在每次访问时进行反射,为每个属性构建一个委托,并通常使其变得清晰。我可能会在博客文章中对此进行调查...但这有点令人讨厌,而且我更喜欢能够仅归因于参数的想法...
编辑:代码已实施,并且博客文章已正式制作。恶心,但很有趣。
一句话:不。
扩展方法传递一个值。它不知道该值来自哪里,也不知道调用者可能选择将其引用为什么标识符。
我发现使用代码片段最简单。
在您的示例中,我可以输入
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>
另请参阅 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
}
}
抱歉,我目前没有时间填写详细信息或提供完整的代码。
我建议您最好执行以下操作:
public static void ThrowIfArgumentIsNull(this object value, string argument)
{
if (value == null)
{
throw new ArgumentNullException(argument);
}
}
在这种情况下使用泛型似乎没有增加任何价值。但至于你原来的问题,我认为这是不可能的。
从 .NET 6 开始,您可以使用以下:
ArgumentNullException.ThrowIfNull(argument);
“是否可以重构这个扩展方法?”
正如其他人已经说过的,如果不使用一些涉及的 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.Minhttps://www.nuget.org/packages/CCX.Common.Shared.Min/