Каков наиболее надежный способ связывания членов с их именем строки?

Различные части платформы .NET требуют использования имени строки свойства:

  • ArgumentException использует имя изменяющей переменной
  • DependencyProperty использует имя свойства, которое оно поддерживает
  • INotifyPropertyChanged использует имя только что измененного свойства.

Самый простой способ заселения этих параметров, по-видимому, является жестким их кодированием (т.е.: new ArgumentNullException("myArg")). Это кажется чрезмерно хрупким не до тех пор, пока вы не поймете, что ваш рефакторинг нарушил связь.

Использование рефлексии для проверки этих параметров - единственное решение, которое выскакивает для меня, но подтверждается, что проверка выполняется только во время выполнения.

Есть ли лучший способ определить отношения между членом и его именем? Предпочтение будет отдано простому, но элегантному исполнению.

Ответы

Ответ 1

Вы можете использовать Expression<Func<T, object>> для этого следующим образом:

using System;
using System.Linq;
using System.Linq.Expressions;

namespace ConsoleApplication3
{
    public class MyClass
    {
        public int IntegralValue { get; set; }

        public void Validate()
        {
            if (this.IntegralValue < 0)
                throw new ArgumentOutOfRangeException(PropertyHelper.GetName<MyClass>(o => o.IntegralValue));
        }
    }

    public static class PropertyHelper
    {
        /// <summary>Extracts the property (member) name from the provided expression.</summary>
        public static string GetName<T>(this Expression<Func<T, object>> expression)
        {
            MemberExpression memberExpression = null;

            if (expression.Body is MemberExpression)
                memberExpression = (MemberExpression)expression.Body;
            else if (expression.Body is UnaryExpression)
                memberExpression = (((UnaryExpression)expression.Body).Operand as MemberExpression);

            if (memberExpression == null)
                throw new ApplicationException("Could not determine member name from expression.");

            return memberExpression.Member.Name;
        }
    }

    public static class Program
    {
        public static void Main(string[] args)
        {
            MyClass good = new MyClass() { IntegralValue = 100 };
            MyClass bad = new MyClass() { IntegralValue = -100 };

            try { good.Validate(); }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }

            try { bad.Validate(); }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }

            Console.ReadKey();
        }
    }
}

Выход

System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
Parameter name: IntegralValue
    at ConsoleApplication3.MyClass.Validate() in d:\...\ConsoleApplication3\Program.cs:line 14
    at ConsoleApplication3.Program.Main(String[] args) in d:\...\ConsoleApplication3\Program.cs:line 50

Объяснение

Это позволит вам использовать lambda для ссылки на имена свойств. Метод GetName проверяет предоставленное выражение и извлекает имя указанного вами члена. Таким образом, при переименовании свойства и реорганизации изменения все эти лямбды автоматически обновляются. Больше никаких строк не требуется!

Ответ 2

Вы можете использовать оператор выражения для связывания имени.

throw new SomeException<MyType>(x => x.myArg)

Где выражение следует Expression<Func<TSource, TValue>>. Затем, рефакторинг и то, что фактически не меняет выражение. Затем ваше исключение анализирует выражение и вытаскивает имя из свойства.

Ответ 3

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

(Тем не менее, остерегайтесь ошибка в Visual Studio, из-за которой все еще возможно запустить неудачную сборку, просто нажав "Запустить" второй раз, не внося никаких изменений...)

Как вы планируете выполнять проверку? Если вы хотите использовать только встроенные функции отражения .NET, вам нужно будет изучить метод IL как необработанные байты. Этот класс ILReader поможет вам превратить эти байты в нечто значимое, что вы можете проанализировать.

Ответ 4

Я использовал PostSharp (фреймворк AOP для .net) для размещения разбросанных фрагментов в одном месте (arg и его имя), например:

void MyMethod([NotNull] object arg1) {}

автоматически генерирует код MSIL (во время компиляции), чтобы выполнить проверку и выбросить исключение.

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