Is it possible to refactor this extension method?
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?
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.
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.
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>
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.
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.
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.
With C# 10 you can do this ArgumentNullException.ThrowIfNull(argument);
"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 */ $"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/
精彩评论