Разрешение имени параметра во время выполнения

Возможный дубликат:
Поиск имени переменной, переданного функции в С#

В С#, есть ли способ (лучше), чтобы разрешить имя параметра во время выполнения?

Например, в следующем методе, если вы переименовали параметр метода, вам также необходимо будет запомнить строковый литерал, переданный в ArgumentNullException.

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

        // ..
    }

Ответы

Ответ 1

Один из способов:

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

Этот код также требует вспомогательной функции:

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

В основном код работает, определяя новый анонимный тип с единственным свойством, состоящим из имени параметра, который вы хотите. Затем GetName() использует отражение для извлечения имени этого свойства.

Здесь есть более подробная информация: http://abdullin.com/journal/2008/12/13/how-to-find-out-variable-or-parameter-name-in-c.html

Ответ 2

Короткий ответ: Нет, нет. (Достаточно ли это достаточно?;)

(EDIT: Ответ Justin, вероятно, имеет значение. Он оставляет неприятный вкус во рту, но он достигает цели "не нужно помещать имя параметра в строку". Не думаю, что я действительно считаю AOP хотя, поскольку это действительно меняется на совершенно другой подход, а не на исходный вопрос получения имени параметра внутри метода.)

Более длинный ответ: есть способ узнать все параметры метода, но я не думаю, что это полезно в этом случае.

Вот пример, который отображает имена параметров из нескольких методов:

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);
        }
    }
}

Итак, что делать, но если у вас есть несколько параметров, и вы хотели создать подходящее исключение, как бы вы знали (безопасным образом), который использовать? В идеале вы хотите что-то вроде:

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

    // ..
}

где мифический оператор infoof вернет a ParameterInfo. К сожалению, этого не существует.

Ответ 3

Я занимался этой самой проблемой. Существует несколько способов получить имя параметра, но наиболее эффективным является падение в IL. Вы можете увидеть пример моей реализации в моем сообщении в блоге по этой самой теме Принимая боль от проверки параметров.

Опираясь на этот подход, вам нужно передать имя параметра в качестве делегата, но небольшая цена за чистый код:

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

Это несколько чище и яснее, чем:

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");
    }
}

Метод статического метода позволил мне объединить ряд методов в свободном интерфейсе. Первоначально возвращается объект Argument, который разрешает только базовый нулевой тест, который возвращает объект ReferenceArgument, который затем может иметь дополнительную проверку. Если тестируемый объект является типом значения, тогда доступны различные тесты.

API допускает ряд общих тестов, но было бы сложно зафиксировать все возможные тесты, чтобы обеспечить гибкость. Общий метод тестирования позволяет предоставлять выражение или функцию, а в случае первого выражение может фактически как сообщение об ошибке.

Мой пример охватывает только некоторые из основ, но вы можете легко расширить интерфейс, чтобы проверять диапазоны и бросать ArgumentOutOfRangeExceptions или объекты тестирования наследовать из определенного базового класса или реализовывать интерфейс. Есть несколько подобных реализаций, но я еще не видел таких, которые получают имя параметра.

Ответ 4

Вы можете получить эту информацию с помощью АОП. Вы можете определить перехват, который вызывается перед выполнением метода, и выделять там исключение. Это также заботится о том, что нулевая проверка является сквозной проблемой.

PostSharp - хорошая простая реализация АОП.

Вот как выглядел бы ваш код (не тестировался, но он должен быть очень близко)

[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);
    }
}

Теперь вы можете изменить свой метод:

[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
}

Ответ 5

Возможно, вы захотите:

1)

public static void ThrowIfNull<T>(Expression<Func<T>> expr)
{
    if (expr == null || expr.Compile()() != null) //the compile part is slow
        return;

    throw new ArgumentNullException(((MemberExpression)expr.Body).Member.Name);
}

или

2)

public static void ThrowIfNull<T>(Expression<Func<T>> expr)
{
    if (expr == null)
        return;

    var param = (MemberExpression)expr.Body;
    if (((FieldInfo)param.Member).GetValue(((ConstantExpression)param.Expression).Value) == null)
        throw new ArgumentNullException(param.Member.Name);
}

И назовите его:

Class.ThrowIfNull(() => resource);

Но это не то, что вы хотели бы, вероятно. Его также намного медленнее 1) примерно в 1000 раз медленнее, чем 2). Может быть:

3)

public static void ThrowIfNull<T>(this T item) where T : class
{
    if (item == null)
        return;

    var param = typeof(T).GetProperties()[0];
    if (param.GetValue(item, null) == null)
        throw new ArgumentNullException(param.Name);
}

И назовите его:

new { resource }.ThrowIfNull();

Чище, намного быстрее, чем выше 2!:)

Вы также можете расширить эти методы для свойств объектов. Например,

new { myClass.MyProperty1 }.ThrowIfNull();

Вы можете кэшировать значения свойств, чтобы повысить производительность, поскольку имена свойств не изменяются во время выполнения. См. Соответствующий вопрос Поиск имени переменной, переданного функции