15

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?

GWLlosa
  • 23,995
  • 17
  • 79
  • 116
  • It took 6 or 7 passes before I could spot the difference in your two lines of code. – jjnguy Apr 27 '09 at 20:45
  • Welcome to my hell... now imagine it littered with acronyms like CPCR, CPR, CLI, etc... – GWLlosa Apr 27 '09 at 20:46
  • For the longest time I've wished for some kind of VS add in that would parse out all the strings in your code and spell check them. It would also take into account camel casing, and spell check each word individually. Someone needs to write that sucker.... – BFree Apr 27 '09 at 20:51
  • @BFree - The Agent Smith plugin for Resharper does exactly that, but a free option is to get DXCore from here: http://www.devexpress.com/Products/Visual_Studio_Add-in/DXCore/ and add the spell check plugin from here: http://www.rthand.com/DesktopModules/Articles/ArticlesView.aspx?tabID=0&alias=RightHand&lang=en-US&ItemID=4&mid=10244. Though I'm not sure of it's support for CamelCase in strings... – Martin Harris Apr 27 '09 at 21:13
  • 1
    @BFree - no, what we need is the missing `nameof(Foo)` or `memberof(Foo)` keyword/methods. We keep asking and hoping, but nothing yet. Until then, Expressions are the closest that I know of. – Marc Gravell Apr 27 '09 at 21:16
  • Isn't this what Bindings are for? Or is there some other requirement I'm missing. – user7116 Sep 09 '09 at 17:53
  • Even Bindings use the strings, which is the problem we're rying to solve here. – GWLlosa Sep 10 '09 at 14:35
  • possible duplicate of [Get property name and type using lambda expression](http://stackoverflow.com/questions/273941/get-property-name-and-type-using-lambda-expression) – nawfal Apr 27 '13 at 14:25

6 Answers6

15

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);
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
3

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.

Steve Adams
  • 561
  • 4
  • 8
  • I can see some comments relating to the use of the extension method, the reason I wanted to get my hands on this was for WPF Binding (mvvm) so you can call OnPropertyChanged(this.GetProperty(() => MyProperty); Thus helping alleviate some of the problems around compile time safety of such wpf binding. At least if you change the property name you get some compile time safety. You still have to edit the binding text in your xaml though... – Steve Adams Feb 14 '13 at 03:00
3

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));
MadManMarkAu
  • 150
  • 1
  • 7
2

Consider using lambdas or even System.Linq.Expressions for this, with one of:

extern void AddMapping<T,U>(Control control, T target, Func<T,U> mapping);
extern void AddMapping<T,U>(Control control, T target, Expression<Func<T,U>> mapping);

Then call it with

this.AddMapping(this.myControl, myObject, (x => x.PropertyName));

Use the Expression argument if you need to take apart the abstract syntax tree at runtime, to do reflective things like obtain the property name as a string; alternatively, let the delegate do the job of fishing out the data you need.

Jeffrey Hantin
  • 35,734
  • 7
  • 75
  • 94
1

What are you looking for is called Static Reflection. Shameless plug => http://emiajnet.blogspot.com/2009/05/getting-fun-with-net-static-reflection.html And a much better article here: http://www.lostechies.com/blogs/gabrielschenker/archive/2009/02/03/dynamic-reflection-versus-static-reflection.aspx

Jaime Febres
  • 1,267
  • 1
  • 10
  • 15
1

You really shouldn't be passing String literals in as property names. Instead you should be using YourClass.PROPERTY_NAME_FOO.

You should declare these Strings as consts in your class.

public const String PROPERTY_NAME_FOO = "any string, it really doesn't matter";
public const String PROPERTY_NAME_BAR = "some other name, it really doesn't matter";

Or, you don't have to worry about Strings at all, just property names:

public const int PROPERTY_NAME_FOO = 0;
public const int PROPERTY_NAME_BAR = 1; //values don't matter, as long as they are unique

That would stop strings that don't refer to a valid property from getting into function calls.

Intelisense will be able to show you the property names from within your class as suggestions for auto complete.

jjnguy
  • 136,852
  • 53
  • 295
  • 323
  • I thought of that, the only thing that bothers me is that its redundant... You're now maintaining property information in 2 seperate places (the const string and the property name itself) which seems less than optimal. – GWLlosa Apr 27 '09 at 20:52
  • You really don't have to use a String. You could just map them to ints or whatever. It is the name that is important, and the fact that the values are unique. – jjnguy Apr 27 '09 at 20:53
  • But if I don't map them to strings, how do I eventually get a string value? – GWLlosa Apr 27 '09 at 21:02
  • Well, if you need some sort of string literal for printing then you would have to use strings. Otherwise, always use the property name from the class to refer to the property, not the int/string value. – jjnguy Apr 27 '09 at 21:04
  • There is no reason to display the literal value of the property name, as long as you know the property name, you don't need any more information. – jjnguy Apr 27 '09 at 21:06
  • Intelisense will be able to show you the property names from within your class as suggestions for auto complete. – jjnguy Apr 27 '09 at 21:08
  • Sometimes, the names are important - if you are using data-binding etc. Which is especially painful if you use obfuscation ;-p Of course, using things like Expression side-steps this, by using the MemberInfo directly. – Marc Gravell Apr 27 '09 at 21:12