开发者

What is the most robust way of linking members with their string name?

Various parts of the .NET framework require the use of the strin开发者_运维百科g name of a property:

  • ArgumentException uses the name of the offending variable
  • DependencyProperty uses the name of the property it backs
  • INotifyPropertyChanged uses the name of the property which just changed.

The easiest approach to populate these parameters seems to be hard coding them (ie: new ArgumentNullException("myArg")). This seems excessively fragile it's not until runtime that you'll realize your refactoring broke the association.

Using reflection to validate these parameters is the only solution that jumps out to me but said validation is till only performed at run-time.

Is there a better way of defining the relationship between a member and it's name? Preference will be given to a simple but elegant design-time enforcement.


You can use an Expression statement to link the name.

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

Where the expression follows Expression<Func<TSource, TValue>>. Then, refactorings and what not actually change the expression. Your exception then parses the expression and pulls the name from the property.


You can just write your reflection code to do the validation, and then run it in a post-build step. That way the validation step becomes part of the compilation process and is reported pretty much like a compile error.

(However, beware of a bug in Visual Studio due to which it is still possible to run a failed build by simply pressing “Run” a second time without making any changes...)

How are you planning to do the validation? If you want to use just the .NET’s built-in reflection capabilities, you will have to examine the method IL as raw bytes. This ILReader class will help you turn those bytes into something meaningful that you can analyse.


You can use an Expression<Func<T, object>> to do this as follows:

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

Output

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

Explanation

This will allow you to use a lambda to reference property names. The GetName method inspects the provided expression and extracts the name of the member that you've specified. This way, when you rename a property and refactor the change, all of these lambdas are automatically updated. No more strings needed!


I used to use PostSharp (an "AOP" framework for .net) to put scattered pieces in one place (arg and its name), for example:

void MyMethod([NotNull] object arg1) {}

would automatically generate MSIL code (at compile time) to do the validation and throw the exception.

It addresses your example, it probably can made be work for other related scenarios.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜