可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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?
回答1:
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);
回答2:
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.
回答3:
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.
回答4:
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.
回答5:
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