Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

.net

Various parts of the .NET framework require the use of the string 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.

like image 674
Chris Kerekes Avatar asked May 04 '11 19:05

Chris Kerekes


3 Answers

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.

like image 106
Tejs Avatar answered Nov 16 '22 21:11

Tejs


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.

like image 3
Timwi Avatar answered Nov 16 '22 20:11

Timwi


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!

like image 1
Josh M. Avatar answered Nov 16 '22 20:11

Josh M.