如何推断基类中派生类的类型?

时间:2011-02-28 15:24:35

标签: c# generics type-inference

我想创建一个方法,允许我更改从我的基类派生的类的任意属性,结果应如下所示:SetPropertyValue("size.height", 50); - 其中size是我派生的属性class heightsize的属性。

我差不多完成了我的实现,但是在继续之前还有一个我想要解决的最后一个障碍,为了描述这个我首先要解释一下我的实现:

  • 可以修改的属性使用属性
  • 进行修饰
  • 我的基类中有一个方法可以搜索所有派生类及其修饰属性
  • 对于每个属性,我生成一个“属性修饰符”,一个包含2个委托的类:一个用于设置,另一个用于获取属性的值。
  • 属性修饰符存储在字典中,属性名称为键
  • 在我的基类中,还有另一个包含所有property-modifier-dictionaries的字典,其中相应类的Type为键。

SetPropertyValue方法的作用是:

  • 使用派生类的具体类型(< - 尚未解决)获取正确的property-modifier-dictionary
  • 获取要更改的属性的属性修饰符(例如,属性size
  • 使用get或set委托修改属性的值

进一步澄清的一些示例代码:

private static Dictionary<RuntimeTypeHandle, object> EditableTypes; //property-modifier-dictionary

protected void SetPropertyValue<T>(EditablePropertyMap<T> map, string property, object value) {
  var property = map[property]; // get the property modifier
  property.Set((T)this, value); // use the set delegate (encapsulated in a method)
}

在上面的代码中,T是实际(派生)类的类型。我需要这种类型的get / set委托。问题是当我不知道T是什么时如何获得EditablePropertyMap<T>

我当前(丑陋)的解决方案是在派生类的覆盖虚拟方法中传递地图:

public override void SetPropertyValue(string property, object value) {
  base.SetPropertyValue((EditablePropertyMap<ExampleType>)EditableTypes[typeof(ExampleType)], property, value);
}

这样做是:使用类的类型获取包含此类的属性修饰符的正确字典,将其强制转换为适当类型并将其传递给SetPropertyValue方法。

我想摆脱派生类中的SetPropertyValue方法(因为有很多派生类),但还不知道如何实现它。我不能只创建一个虚拟GetEditablePropertyMap<T>方法,因为我无法推断T的具体类型。我也无法直接使用类型访问我的字典并从中检索EditablePropertyMap<T>因为我无法从基类中的object转换为它,因为我再次不知道T。< / p>

我发现了一些推理类型的巧妙技巧(例如通过添加虚拟T参数),但不能将它们应用于我的特定问题。我非常感谢您对我提出的任何建议。

编辑: 虽然这已经是一面文字了,但我还要补充一些注意事项:

  • 表现很重要。我可以(并且确实)在启动时使用一次来构建我的字典和委托,但是在运行时它是不可接受的(除非你能说服我它与委托/方法调用一样快; )
  • 我将EditablePropertyMap存储为对象,因为我找不到其他方式(不同的泛型类型参数) - 也许这也可以改进。
  • 我不能像Mehrdad建议的那样使用奇怪的重复模板模式。由于其他原因,我的基类不能通用(与手头的问题无关)。

澄清EditableProperty / EditablePropertyMap的作用:

public class EditableProperty<T> {
  private Func<T, object> getter;
  private Action<T, object> setter;
  ...
  public object Get(T obj) {  return getter(obj); }
  public void Set(T obj, object value) { setter(obj, value);}

地图仅仅是Dictionary<string, EditableProperty<T>>的包装。

3 个答案:

答案 0 :(得分:1)

我不确定你是否愿意改变设计,但我认为curiously recurring template pattern可能就是你所需要的:

class Super<TDerived> where TDerived : Super<TDerived>
{
    //Now we have access to TDerived here
}

class Derived : Super<Derived> { ... }

你也可以试着说:

typeof(MyClass).GetMethod("SetPropertyValue")
    .MakeGenericMethod(new Type[] { derivedType })
        .Invoke(dictionary, object[] { map, property, value });

但这会非常慢。

如果在编译时不知道类型,就无法快速完成此操作,因为根据定义,您必须在编译时绑定方法以避免运行时命中。

答案 1 :(得分:1)

http://msdn.microsoft.com/en-us/library/system.object.gettype.aspx - 有一个由Type键入的EditablePropertyMaps地图,在SetPropertyValue中通过GetType()获取派生类型的地图。我错过了什么吗?

此外:

public abstract class EditablePropertyMap { }
public class EditablePropertyMap<T> : EditablePropertyMap { }

编辑:我想我很难建议放弃泛型并传递对象,但现在我看到你想让代表保持通用。下面怎么样 - 我重新安排了一些东西,但它没有改变主旨:

abstract class EditableProperty { }

class EditableProperty<T> : EditableProperty {
    public void Set(T obj, object value) { Console.WriteLine(value); }
}

abstract class EditablePropertyMap {
    private static Dictionary<Type, EditablePropertyMap> maps = new Dictionary<Type, EditablePropertyMap>();

    abstract public void AddProperty(string name, EditableProperty property);
    abstract public void SetPropertyValue(object obj, string name, object value);

    static public EditablePropertyMap Get(Type t) {
        EditablePropertyMap map;

        if(!maps.TryGetValue(t, out map)) {
            map = (EditablePropertyMap)Activator.CreateInstance(
                typeof(EditablePropertyMap<>)
                    .MakeGenericType(t));

            maps.Add(t, map);
        }

        return map;
    }
}

class EditablePropertyMap<T> : EditablePropertyMap {
    private Dictionary<string, EditableProperty<T>> properties = new Dictionary<string, EditableProperty<T>>();

    public override void AddProperty(string name, EditableProperty property) {
        properties.Add(name, (EditableProperty<T>)property);
    }

    public override void SetPropertyValue(object obj, string name, object value) {
        EditableProperty<T> property = properties[name];
        property.Set((T)obj, value);
    }
}

class DynamicObjectBase {
    public void SetPropertyValue(string name, object value) {
        EditablePropertyMap map = EditablePropertyMap.Get(GetType());
        map.SetPropertyValue(this, name, value);
    }
}

class SomeDynamicObject : DynamicObjectBase {

}

class Program {
    static void Main(string[] args) {
        EditablePropertyMap map = EditablePropertyMap.Get(typeof(SomeDynamicObject));
        map.AddProperty("wut", new EditableProperty<SomeDynamicObject>());

        SomeDynamicObject o = new SomeDynamicObject();
        o.SetPropertyValue("wut", 1);
    }
}

我也没有做过在init上只使用类型创建EditableProperties的部分,但我认为你已经完成了所有这些。

答案 2 :(得分:0)

由于您无论如何都是从基类转换到派生类(在SetPropertyValue中),为什么不将转换放在生成的修饰符委托中?然后SetPropertyValue不再需要通用。

作为旁注,我知道你说性能很关键,但你有没有使用例如缓存PropertyInfo个实例进行获取和设置?