Reputation: 604
How to let an Attribute in one property know the existence of another property?
Lets say i have this class, and like this, many others:
public class MyClass
{
[CheckDirty] //a custom attribute (that it is empty for now)
public int A { get; set; }
public int B { get; set; }
public string Description { get; set; }
public string Info { get; set; }
}
Somewhere in our program, if we want to see if an object changed values on any CheckDirty
property, for example lets say it is diferent from DB, MyPropertyUtils.GetPropertiesIfDirty()
does this, giving us an array of changed propertys, on any property with that attribute:
PropertyInfo[] MyPropertyUtils.GetPropertiesIfDirty(SomeBaseObject ObjectFromDB, SomeBaseObject NewValues);
Perfect.
So, lets say A
changed and in this case Info
holds some information we need(in another class might be any other property). If we want 'A' we just do property.GetValue(NewValues, null);
But we dont want 'A's value, we want 'A' or CheckDirty
to tell us where to read some data we want. How can i tell my attribute CheckDirty
where to get the values from?
I was thinking in giving an expression to CheckDirty
but an Attribute's argument "must be a constant expression, typeof expression or array creation expression of an attribute parameter type"(thats what VS says).
So I decided, "ok, lets give it a string with the property's name", and so my try failed:
(this is all the code we need to work on, the rest was just to give some kind of context example)
public class CheckDirty : Attribute
{
public String targetPropertyName;
public CheckDirty(String targetPropertyName)
{
this.targetPropertyName = targetPropertyName;
}
}
public class MyClass
{
//Code fails on this line
[CheckDirty(BoundPropertyNames.Info)]
public int Id { get; set; }
public string Info { get; set; }
public static class BoundPropertyNames
{
public static readonly string Info = ((MemberExpression)
((Expression<Func<MyClass, string>>)
(m => m.Info)
).Body
).Member.Name;
}
}
This is the error i get:
An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type
We do NOT want to pass the name of the proprety as a String
saing [CheckDirty("Info")]
because that way if anyone in the future changes the class, and in concrete the property's name, no error would get thrown in compile time by it, only occuring the error in run time, when an "edit" to that field would occur. Or maybe it would just not do anything because it could not find the property.
Any idea how to not use the strongly typed string as a property name?
Upvotes: 0
Views: 75
Reputation: 4280
You may use something like this, first declare an interface that will be implemented by every class that need dirty checking:
interface IDirtyCheckPropertiesProvider {
string GetPropertyName(string dirtyProperty);
}
then implement it like that
class DataEntity : IDirtyCheckPropertiesProvider {
[CheckDirty]
public int Id { get; set; }
public string Info { get; set; }
string GetPropertyName(string dirtyProperty) {
if (GetPropertyNameFromExpression(x => Id) == dirtyProperty)
return GetPropertyNameFromExpression(x => Info);
return null;
}
}
In class that will be responsible for handling dirty checks you must use this interface to get target property names. There is a bit too much boilerplate that may be removed further by using Reflection API.
On the other hand using string for property names looks like more simple solution. If you use tool like Resharper - using string is a viable option - Resharper will automatically refactor string when you change property name. Also for a long time string'ed property names were used in implementation of WPF INotifyPropertyChanged.
As comments suggested nameof
is the best option in VS2015.
Upvotes: 1