“合并”类型,不使用反射或复制代码?

时间:2010-01-15 16:25:38

标签: c#

似乎没想到一个好头衔,我的道歉。随意将其改为更合适的东西;我很感激。

我在这里处于一个不寻常的位置(或者可能不那么不寻常)。我有一个基类型,从中可以分支许多类型,这些类型扩展了它的功能但仍保持相当一致。其中一些或多或少会重复,但以不同的方式呈现数据。它们都可以以相同的方式进行管理,所以我决定创建一个“合并”类,它将采用两个或更多这些对象并允许同时控制它们,但我已经使用了使用反射。如果我要重新定义基类的所有成员并简单地重定向所有sets / gets / etc,那么它只是简单的错误,因为如果我正在“合并”类型的基类是不断变化,合并阶级也必须改变。

但这会导致我认为可以避免的性能成本。性能不仅来自反射,还来自反射过程中可预测的装箱/拆箱

这是我正在做的一个伪代码示例:

class SomeBase
{
    public virtual bool SomeBool { get; set; }
}

class SomeDerived : SomeBase
{
    // ... extends SomeBase
}

class SomeMerger
{
    private SomeBase[] collection;

    public SomeMerger(SomeBase[] collection)
    {
        this.collection = collection;
    }

    public void SetProperty(string propertyName, object value)
    {
        for (int i = 0; i < this.collection.Length; i++)
        {
            PropertyInfo pi = collection[i].GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
            if (pi != null)
                pi.SetValue(collection[i], value, null);
        }
    }

    // .. etc
}

现在,我喜欢能够做的就是访问成员,好像它们是合并类中的单个实体一样(例如“SomeMergerObject.SomeBool = true”会设置所有它合并为true的所有对象中的SomeBool属性,使语法更自然)。但是,我能看到这样做的唯一方法是重新定义基类的所有方法和属性,并将调用重定向到它们(我认为这不会被认为是正确的)。是否有更清洁/更好的方法来实现这一目标?

很抱歉,如果我在解释这个问题上表现不佳。大喊大叫如果你感到困惑,我会试着澄清一下。 :)

修改

我想我需要澄清一下。当我说“我有这种基本类型等”时,我认为我误导了 - 实现是现实的,而不是我的,所有我试图做的就是让它变得更容易与...合作。我没有为几个对象设置基本属性,而是在可见状态(例如)等区域中共享,我认为这将是一个很好的功能(尽管是一个微不足道的功能,而且工作量超过了它的价值......但出于好奇心的考虑,为什么不探索这个想法?)使一组对象表现为一个。这甚至没有接近一个问题,只是一个我觉得要调情的改进想法。

我并不是在暗示“一种新的语言功能”,而我的问题的基调是是否有办法实现这一目标,这是一种干净正确的方式。我想我的询问非常糟糕,抱歉。

4 个答案:

答案 0 :(得分:4)

听起来你无缘无故地创造了维护噩梦。你的问题并不是那么糟糕,你必须发明基本上是一种新的语言特征。

除此之外,如果您有一组仅在它们如何呈现信息方面有所不同的类型,那么您可以使用除继承之外的其他机制来打破公共代码。例如,您可以将委托传递给他们的“渲染”方法,或者使用策略模式。

答案 1 :(得分:1)

如果我正确解释了问题,请使用the code from here

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
public interface ISomeInterface {
    void SetSomeBool(bool value);
}
class SomeBase : ISomeInterface {
    public virtual bool SomeBool { get; set; }
    void ISomeInterface.SetSomeBool(bool value) { SomeBool = value; }
}
class SomeDerived : SomeBase {
    // ... extends SomeBase
}
static class Program {
    static void Main() {
        var item1 = new SomeBase();
        var item2 = new SomeDerived();
        var items = new List<ISomeInterface> { item1, item2};
        ISomeInterface group = GroupGenerator.Create(items);
        group.SetSomeBool(true);
        Console.WriteLine(item1.SomeBool); // true
        Console.WriteLine(item2.SomeBool); // true
        group.SetSomeBool(false);
        Console.WriteLine(item1.SomeBool); // false
        Console.WriteLine(item2.SomeBool); // false
    }
}

请注意,它也适用于属性,但get必须抛出异常(set适用于所有)。出于这个原因,我更喜欢显式方法方法(接口上没有get)。它也可能只是一个set属性,但它们真的罕见:

public interface ISomeInterface
{
    bool SomeBool { set; }
}
class SomeBase : ISomeInterface
{
    public virtual bool SomeBool { get; set; }
}
class SomeDerived : SomeBase
{
    // ... extends SomeBase
}
static class Program
{
    static void Main()
    {
        var item1 = new SomeBase();
        var item2 = new SomeDerived();
        var items = new List<ISomeInterface> { item1, item2};
        ISomeInterface group = GroupGenerator.Create(items);
        group.SomeBool = true;
        Console.WriteLine(item1.SomeBool); // true
        Console.WriteLine(item2.SomeBool); // true
        group.SomeBool = false;
        Console.WriteLine(item1.SomeBool); // false
        Console.WriteLine(item2.SomeBool); // false
    }
}

答案 2 :(得分:0)

好像你在这里走了一条奇怪的道路。如果你在编译时不知道你正在处理什么类型,你来使用反射。

但是,您可以在.net 4.0中使用新的动态关键字,并让您的类实现IDynamicMetaObjectProvider以将调用站点检查推迟到运行时。

答案 3 :(得分:0)

此代码使用来自.NET 4的ConcurrentDictionary<TKey, TValue>,但您可以使用Dictionary<TKey, TValue>编写它,并使其不是线程安全的或使用粗锁定(lock语句)并牺牲一点表现。

public static class DynamicObjects
{
    private static readonly ConcurrentDictionary<Type, ConcurrentDictionary<string, Action<object, object>>> _setters
        = new ConcurrentDictionary<Type, ConcurrentDictionary<string, Action<object, object>>>();

    public static void SetProperty(object instance, string property, object value)
    {
        if (instance == null)
            throw new ArgumentNullException("instance");
        if (property == null)
            throw new ArgumentNullException("property");
        if (property.Length == 0)
            throw new ArgumentException("The property name cannot be empty.", "property");

        Type type = instance.GetType();
        var settersForType = _setters.GetOrAdd(type, CreateDictionaryForType);
        var setter = settersForType.GetOrAdd(property, (obj, newValue) => CreateSetterForProperty(type, property));
        setter(instance, value);
    }

    private static ConcurrentDictionary<string, Action<object, object>> CreateDictionaryForType(Type type)
    {
        return new ConcurrentDictionary<string, Action<object, object>>();
    }

    private static Action<object, object> CreateSetterForProperty(Type type, string property)
    {
        var propertyInfo = type.GetProperty(property);
        if (propertyInfo == null)
            return (o, v) => ThrowInvalidPropertyException(type, property);

        var setterMethod = propertyInfo.GetSetMethod();
        if (setterMethod == null)
            return (o, v) => ThrowReadOnlyPropertyException(type, property);

        ParameterExpression instance = Expression.Parameter(typeof(object), "instance");
        ParameterExpression value = Expression.Parameter(typeof(object), "value");
        Expression<Action<object, object>> expression =
            Expression.Lambda<Action<object, object>>(
            Expression.Call(instance, setterMethod, Expression.Convert(value, propertyInfo.PropertyType)),
            instance,
            value);

        return expression.Compile();
    }

    private static void ThrowInvalidPropertyException(Type type, string propertyName)
    {
        throw new InvalidOperationException("The type '" + type.FullName + "' does not have a publicly accessible property '" + propertyName + "'.");
    }

    private static void ThrowReadOnlyPropertyException(Type type, string propertyName)
    {
        throw new InvalidOperationException("The type '" + type.FullName + "' does not have a publicly visible setter for the '" + propertyName + "' property.");
    }
}