【问题标题】:"Merging" types, without using reflection or duplicating code?“合并”类型,不使用反射或复制代码?
【发布时间】:2011-01-05 14:18:46
【问题描述】:

我想不出一个好的标题,我很抱歉。随意将其更改为更合适的内容;我会很感激的。

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

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

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

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 属性,使语法更自然)。但是,我认为这样做的唯一方法是重新定义基类的所有方法和属性并将调用重定向到它们(我认为这不正确)。有没有更清洁/更好的方法来实现这一目标?

抱歉,如果我在解释这方面做得不好。如果您感到困惑,请大喊大叫,我会尽力澄清。 :)

编辑:

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

我并不是在建议“一种新的语言功能”,我的问题的语气是有没有办法做到这一点,一种干净而正确的方法。我想我的询问很糟糕,对此感到抱歉。

【问题讨论】:

  • 这听起来像是你在制造问题。也许您可以发布更多关于您的情况的详细信息 - 对于您想要实现的目标,肯定有更好的设计。

标签: c#


【解决方案1】:

这听起来像是无缘无故制造了一场维护噩梦。您的问题还不错,以至于您必须发明基本上是一种新的语言功能。

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

【讨论】:

  • 我使用反射的原因是为了避免“维护噩梦”。如果您指的是结束位(重新定义所有成员),这就是为什么我要避免它(并且不会实现它)。而且我本身并没有真正的问题,我只是在问我想做的事情是否有可能以一种以后不会成为灾难的方式,如你所说。 :P
  • 我所说的维护噩梦是你使用一个相对复杂的特性(反射)来解决一个相对简单的问题。之后的任何人都必须了解和维护您的反射代码。
  • 我完全同意。这个问题似乎与管理数据无关,而在于它是如何以一种或另一种方式“呈现”的。
【解决方案2】:

如果我正确解释了这个问题,那么使用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-only 属性,但它们真的很少见:

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
    }
}

【讨论】:

  • 我正在写一些类似的东西(但功能较弱),但后来我意识到我的想法(像这样)仍然无法解决能够传递 name 的愿望i> 的待设置属性作为字符串。在这里,您仍然需要在ISomeInterface 中明确列出您可能想要设置的所有属性。如果这是一项硬性要求,我认为无论如何都不能避免反射/样板代码。
  • 我的解释是,这种方法(使用字符串)只是通过反射来说明他现在的工作方式。
  • 您的解释正确。很酷的东西。你的代码直接让我得到了答案,并最终意识到这将是一个糟糕的设计选择。不过,我学到了一些好东西,并且开始摆弄一些实验性代码,不后悔。谢谢。 ;)
【解决方案3】:

看来你在这里走的是一条奇怪的路。如果您在编译时不知道要处理什么类型,则必须使用反射。

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

【讨论】:

    【解决方案4】:

    此代码使用 .NET 4 中的 ConcurrentDictionary&lt;TKey, TValue&gt;,但您可以使用 Dictionary&lt;TKey, TValue&gt; 编写它,要么使其不是线程安全的,要么使用粗锁(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.");
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2021-01-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-14
      • 2018-08-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多