views:

1557

answers:

4

In C#, is there a way (terser the better) to resolve the name of a parameter at runtime?

For example, in the following method, if you renamed the method parameter, you'd also have to remember to update the string literal passed to ArgumentNullException.

    public void Woof(object resource)
    {
        if (resource == null)
        {
            throw new ArgumentNullException("resource");
        }

        // ..
    }
+8  A: 

Short answer: No, there isn't. (Is that terse enough? ;)

(EDIT: Justin's answer probably counts. It leaves a bad taste in my mouth, but it accomplishes the goal of "no need to put the parameter name into a string". I don't think I'd really count AOP though, as that's really changing to a completely different approach rather than answering the original question of getting a parameter name from within a method.)

Longer answer: There's a way to find out all the parameters of a method, but I don't think it's useful in this case.

Here's an example which displays the parameter names from a couple of methods:

using System;
using System.Reflection;

class Test
{
    static void Main()
    {
        Foo(null);
        Bar(null);
    }

    static void Foo(object resource)
    {
        PrintParameters(MethodBase.GetCurrentMethod());
    }

    static void Bar(object other)
    {
        PrintParameters(MethodBase.GetCurrentMethod());
    }

    static void PrintParameters(MethodBase method)
    {
        Console.WriteLine("{0}:", method.Name);
        foreach (ParameterInfo parameter in method.GetParameters())
        {
            Console.WriteLine(" {0} {1}",
                              parameter.ParameterType,
                              parameter.Name);
        }
    }
}

So that does that, but if you have multiple parameters and you wanted to throw an appropriate exception, how would you know (in a safe way) which to use? Ideally you want something like:

public void Woof(object resource)
{
    if (resource == null)
    {
        throw new ArgumentNullException(infoof(resource));
    }

    // ..
}

where the mythical infoof operator would return a ParameterInfo. Unfortunately this doesn't exist.

Jon Skeet
Downvoters: please leave a comment.
Jon Skeet
Thanks for the thorough answer.
frou
+17  A: 

One way:

static void Main(string[] args)
{
  Console.WriteLine("Name is '{0}'", GetName(new {args}));
  Console.ReadLine();
}

This code also requires a supporting function:

static string GetName<T>(T item) where T : class
{
  var properties = typeof(T).GetProperties();
  Enforce.That(properties.Length == 1);
  return properties[0].Name;
}

Basically the code works by defining a new Anonymous Type with a single Property consisting of the parameter who's name you want. GetName() then uses reflection to extract the name of that Property.

There are more details here: http://abdullin.com/journal/2008/12/13/how-to-find-out-variable-or-parameter-name-in-c.html

Justin Ethier
Eewww - and at the same time, neat! I really can't decide whether this counts as a hideous abuse of anonymous types or not.
Jon Skeet
Justin: I strongly suggest you provide a fuller explanation in this answer. Currently it doesn't show anything about how GetName works. The idea in this answer definitely deserves plenty of votes - but could do with being fleshed out.
Jon Skeet
Clever, and it all appears to be supported by the C# spec too, so far as I can tell.
LukeH
Clever! Clever! Clever!
isntn
I like it!Please enter at least 15 characters.Please enter at least 15 characters.Please enter at least 15 characters.Please enter at least 15 characters.Please enter at least 15 characters.Please enter at least 15 characters.Please enter at least 15 characters.
frou
ah ha! what an interesting way around the problem. suck on that reflection namespace!
andy
Just because you can do this, doesn't mean you should. This adds so much more complexity to something that isn't too much of a burden already. Right-clicking and "Refactor" takes care of the original request. Being this "clever" will makes this code much more difficult to maintain - I respectfully vote against this.
Robert Seder
+1  A: 

You can get this information using AOP. You can define an intercept that is invoked before method execution and throw the exception there. This also takes care of the problem that null checking is a cross-cutting concern.

PostSharp is a good simple implementation of AOP.

Here's what your code would look like (haven't tested, but it should get you very close)

[AttributeUsage(AttributeTargets.Parameter)]
public class CanBeNullAttribute : Attribute
{
    private readonly bool canBeNull;

    public CanBeNullAttribute()
        : this(true)
    {
    }

    public CanBeNullAttribute(bool canBeNull)
    {
        this.canBeNull = canBeNull;
    }

    public bool AllowNull
    {
        get { return canBeNull; }
    }
}

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class EnforceNullConstraintAttribute : OnMethodInvocationAspect
{
    public override void OnInvocation(MethodInvocationEventArgs eventArgs)
    {
        object[] arguments = eventArgs.GetArgumentArray();
        ParameterInfo[] parameters = eventArgs.Delegate.Method.GetParameters();

        for (int i = 0; i < arguments.Length; i++)
        {
            if (arguments[i] != null) continue;

            foreach (CanBeNullAttribute attribute in parameters[i].GetCustomAttributes(typeof(CanBeNullAttribute), true))
            {
                if (!attribute.AllowNull) throw new ArgumentNullException(parameters[i].Name);
            }
        }

        base.OnInvocation(eventArgs);
    }
}

Now, you can modify your method:

[EnforceNullConstraint]
public void Woof([CanBeNull(false)] object resource)
{
    // no need to check for null, PostSharp will weave it at compile time

    // execute logic assured that "resource" is not null
}
Michael Meadows
I don't think I'd use it in this case, but that is a very interesting approach for the toolbox. Thanks for bringing it up!
frou
A: 

I dealt with this very same issue. There are a couple of ways of getting the parameter name but the most performant is to dip down into the IL. You can see an example of my implementation on my blog post on this very issue Taking the pain out of parameter validation.

The one caveat to this approach is you need to pass the parameter name in as a delegate but it is small price to pay for cleaner code:

public void SomeMethod(string value)
{
    Validate.Argument(() => value).IsNotNull().IsNotEmpty();
}

Which is somewhat cleaner and clearer than:

public void SomeMethod(string value)
{
    if (value == null)
    {
        throw new ArgumentNullException("value");
    }
    if (value == string.Empty)
    {
        throw new ArgumentException("Value cannot be an empty string.", "value");
    }
}

The static method approach has allowed me to chain a number of methods together in a fluent interface. Initially an Argument object is returned which only allows a basic null test which returns a ReferenceArgument object which can then have additional validation. If the object under test is a value type then different tests are available.

The API allows for a number of common tests but it would be hard to capture all the possible tests so to provide flexibility a generic test method allows an expression or function to be provided and in the case of the former the expression can actually be used as the error message.

My example only covers a few of the basics but you can easily expand the interface to check for ranges and throw ArgumentOutOfRangeExceptions or test objects inherit from a specific base class or implement an interface. There are some similar implementations but I have not as yet seen any that get the parameter name.

Bronumski
I would argue that most developers would scratch their head that that. To me, this crosses the line of being so clever, it's not maintainable. What exactly is so difficult about the extremely common TWO lines of code (compared to your ONE):public void SomeMethod(string value){ if (string.IsNullOrEmpty(value)) throw new ArgumentException("Argument 'value' cannot be null or empty.");}I may be the only one, but I dissent on this approach!
Robert Seder
The issue is that the out of the box approach is over bloated, it takes away from what the method is trying to do by distracting you. Additionally there is a maintenance issue of first putting in the parameter name that is throwing the exception and second maintaining its name when the parameter name changes.All I have done is move this logic into an easy to use fluent interface that comes with less (but still present) clutter. If this type of approach was not need then there wouldn't be so many blog posts pushing their Guard APIs or the Microsoft code contracts for .net 4.0.
Bronumski