Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extracting Property Names For Reflection, with Intellisense and Compile-Time Checking

Ok. So I have some code that maps certain controls on a winForm to certain properties in an object, in order to do certain things to the controls when certain things happen to the data. All well and good, works fine. Not the problem. The issue is, to add items to the mapping, I call a function that looks like:

this.AddMapping(this.myControl,myObject,"myObjectPropertyName");

The problem I run into is that it is very difficult to tell, at compile time, the difference between the above line and the below:

this.AddMapping(this.myControl,myObject,"myObjectPropretyName");

Since the last parameter is a string, there's no compile time checking or anything like that that would enforce that the string itself actually corresponds to a valid property name on the given object. Additionally, things like Refactor and "Find All References" miss out on this sort of reference, resulting in hilarity when the name of the property itself changes. So what I'm wondering is if there's some way to change the function such that what I'm passing in is still a string representing the property name in some way, but with compile time checking of the actual value going in. Someone said I could do this with Expression Trees, but I've read up on them and don't seem to see the connection. I'd love to do something like:

this.AddMapping(this.myControl,myObject,myObject.myObjectPropertyName);

or even

this.AddMapping(this.myControl,myObject.myObjectPropertyName);

would be sweet!

Any ideas?

like image 812
GWLlosa Avatar asked Apr 27 '09 20:04

GWLlosa


3 Answers

in 3.5, Expression is one way to specify member names as code; you could have:

public void AddMapping<TObj,TValue>(Control myControl, TObj myObject,
       Expression<Func<TObj, TValue>> mapping) {...}

and then parse the expression tree to get the value. A little inefficient, but not too bad.

Here's example code:

    public void AddMapping<TSource, TValue>(
        Control control,
        TSource source,
        Expression<Func<TSource, TValue>> mapping)
    {
        if (mapping.Body.NodeType != ExpressionType.MemberAccess)
        {
            throw new InvalidOperationException();
        }
        MemberExpression me = (MemberExpression)mapping.Body;
        if (me.Expression != mapping.Parameters[0])
        {
            throw new InvalidOperationException();
        }
        string name = me.Member.Name;
        // TODO: do something with "control", "source" and "name",
        // maybe also using "me.Member"
    }

called with:

    AddMapping(myControl, foo, f => f.Bar);
like image 174
Marc Gravell Avatar answered Nov 09 '22 22:11

Marc Gravell


To make things easier with the Expression based lamda workaround, I wrote it as an extension method.

  public static string GetPropertyName<T>(this object o, Expression<Func<T>> property)
    {
        var propertyInfo = (property.Body as MemberExpression).Member as PropertyInfo;
        if (propertyInfo == null)
            throw new ArgumentException("The lambda expression 'property' should point to a valid Property");
        var propertyName = propertyInfo.Name;
        return propertyName;
    }

Call like this

    class testclass
    {
        public string s { get; set; }
        public string s2 { get; set; }
        public int i { get; set; }

    }

    [TestMethod]
    public void TestMethod2()
    {
        testclass x = new testclass();
        string nameOfPropertyS = this.GetPropertyName(() => x.s);
        Assert.AreEqual("s", nameOfPropertyS);

        string nameOfPropertyI = x.GetPropertyName(() => x.i);
        Assert.AreEqual("i", nameOfPropertyI);

    }

Okay, using as an extension method is really for convenience as you can in fact call the method on one class for properties of anther class. I'm sure it could be improved.

like image 27
Steve Adams Avatar answered Nov 09 '22 21:11

Steve Adams


Old question, but nobody has updated the answers with more recent syntactic sugar that has become available.

As of C# 6, there is a much better way if getting a member name, while still referring to the member itself. The nameof keyword. It's syntactic sugar that will substitute in a string containing the name of the property/member/function referenced. Intellisense, refactoring, find-all-references will all work. This will work on any property, field, function, and class reference.

using System;

public class Program
{
    public static void Main()
    {
        Console.WriteLine("Name of the class is: {0}", nameof(MyClass));
        Console.WriteLine("Name of the class field is: {0}", nameof(MyClass.MyField));
        Console.WriteLine("Name of the class property is: {0}", nameof(MyClass.MyProperty));
        Console.WriteLine("Name of the class function is: {0}", nameof(MyClass.MyFunction));
    }
}

public class MyClass
{
    public int MyField;

    public int MyProperty { get; set; }

    public void MyFunction() { }
}

This program outputs the following lines:

Name of the class is: MyClass
Name of the class field is: MyField
Name of the class property is: MyProperty
Name of the class function is: MyFunction

Using this, your code now becomes:

this.AddMapping(this.myControl, nameof(myObject.myObjectPropertyName));
like image 3
MadManMarkAu Avatar answered Nov 09 '22 21:11

MadManMarkAu