Reputation: 2569
We have a BaseClass
and a set of derived POCO classes (see DerivedClassA
). They map to an existing key-value store in the database that we cannot change at this point.
Each property on the derived class will map to a key in the store. The keys that the properties represent are string values that are either the property name or (if present) the custom attribute MyAttribute.Key
as demonstrated on PropertyC
below. The common use case for the attribute is if the key begins with an integer which is invalid for a C# property name (and we cannot change the keys).
public class BaseClass
{
public int BaseClass Id { get; set; } = 0;
}
public class DerivedClassA : BaseClass
{
public int PropertyA { get; set; }
public int PropertyB { get; set; }
[MyAttribute(Key = "404Property")]
public int PropertyC { get; set; }
}
In code, we need to get the key values as strings. After some wrestling and digging from other SO Answers (I do not claim any level of expertise with generics), we came up with the GetKey()
method in the derived BaseClass<T>
below. Note that GetCustomAttributeValue<T>()
is a custom helper method that returns the value of an attribute (there may be a better way to do that, but that's out of scope for this question).
public class BaseClass<T> : BaseClass where T : BaseClass<T>
{
public string GetKey(Expression<Func<T, object>> property)
{
var memberInfo = GetMemberInfo(property);
return GetAttributeKey(memberInfo) ?? memberInfo?.Name;
}
private static MemberInfo GetMemberInfo(Expression<Func<T, object>> property) =>
(property.Body as MemberExpression ?? ((UnaryExpression)property.Body).Operand as MemberExpression)?.Member;
private static string GetAttributeKey(MemberInfo member) =>
member.GetCustomAttributeValue<string>(typeof(MyAttribute), "Key");
}
This solution seems to work if we derive the classes from the new BaseClass<T>
public class DerivedClassA : BaseClass<T> {
...
}
The GetKey()
is now called as follows:
var derivedClassA = new DerivedClassA();
var propertyCKey = derivedClassA.GetKey(p => p.PropertyC);
We have a requirement that BaseClass
needs to stay around, however, and we do not want the complexity of both the non-generic and the generic versions of BaseClass
.
When I try to move GetKey()
into the non-generic BaseClass
, it no longer has the T
type of the derived class which it needs to lookup the set of properties on the derived class. I do not want to add duplicate GetKey()
s in each derived class.
Question:
Is there a way to move the GetKey()
method (possibly re-writing it) into BaseClass
rather than introducing the new BaseClass<T>
only for supporting GetKey()
?
Additional Background:
We are trying to wrap object-oriented/strong typing around a data store that is just a table that looks like:
| PreferenceId | UserId | PreferenceString |
Each derived class represents a different PreferenceId
. Each PreferenceString
is just a string of key/values "serialized" in a way custom to that PreferenceId
(there is no rhyme/reason that can be shared across all PreferenceIds
). This should all be redesigned at some point, but we are trying to wrap the current store in some kind of strong typing as a step in the transition.
Upvotes: 4
Views: 892
Reputation: 34244
As for me, all this structure in general seems to be crazy and overly complex.
Consider rewriting the whole approach instead of changing the GetKey
method.
In general, GetKey
in your base class breaks single-responsibility principle.
If I had to do this, I would just extract this functionality into a static class:
public static class CustomKeysHelper
{
public static string GetKey<T>(Expression<Func<T, object>> property) where T : BaseClass
{
var memberInfo = GetMemberInfo(property);
return GetAttributeKey(memberInfo) ?? memberInfo?.Name;
}
private static MemberInfo GetMemberInfo<T>(Expression<Func<T, object>> property) =>
(property.Body as MemberExpression ?? ((UnaryExpression)property.Body).Operand as MemberExpression)?.Member;
private static string GetAttributeKey<T>(MemberInfo member) =>
member.GetCustomAttributeValue<string>(typeof(MyAttribute), "Key");
}
// Usage:
string cKey = CustomKeysHelper.GetKey<DerivedClassA>(dca => dca.PropertyC);
Yes, it makes every GetKey
call look longer, but it separates this logic and makes your intention clear.
Just in case you have instance of object BaseClass
and want to extract property name from instance, but not type, then you can an extension method:
public static class CustomKeysHelper
{
// ... see above
public static string GetKey<T>(this T obj, Expression<Func<T, object>> property) where T : BaseClass
{
return GetKey<T>(property);
}
}
// Now, you can do this:
DerivedClassA derAInstance = ...;
derAInstance.GetKey(dca => dca.PropertyC);
Upvotes: 3