12

我试图找到一种更好的方法来处理一些不断增长的if构造来处理不同类型的类。这些类最终是不同值类型(int、DateTime 等)的包装器,并带有一些额外的状态信息。因此,这些类之间的主要区别在于它们包含的数据类型。虽然它们实现了泛型接口,但它们还需要保存在同构集合中,因此它们还实现了非泛型接口。类实例根据它们所代表的数据类型进行处理,并且它们的传播继续或不继续基于此。

虽然这不一定是 .NET 或 C# 问题,但我的代码是用 C# 编写的。

示例类:

interface ITimedValue {
 TimeSpan TimeStamp { get; }
}

interface ITimedValue<T> : ITimedValue {
 T Value { get; }
}

class NumericValue : ITimedValue<float> {
 public TimeSpan TimeStamp { get; private set; }
 public float Value { get; private set; }
}

class DateTimeValue : ITimedValue<DateTime> {
 public TimeSpan TimeStamp { get; private set; }
 public DateTime Value { get; private set; }
}

class NumericEvaluator {
 public void Evaluate(IEnumerable<ITimedValue> values) ...
}

我提出了两个选择:

双重派送

我最近了解了访问者模式及其使用双重调度来处理这种情况。这很有吸引力,因为它允许不想要的数据不传播(如果我们只想处理一个 int,我们可以以不同于 DateTime 的方式处理它)。此外,如何处理不同类型的行为将仅限于处理调度的单个类。但是如果/当必须支持新的值类型时,需要进行相当多的维护。

联合班

包含支持的每种值类型的属性的类可能是这些类中的每一个存储的内容。对值的任何操作都会影响相应的组件。这比双分派策略更简单,维护更少,但这意味着每条数据都会不必要地传播,因为你不能再按照“我不对那种数据类型进行操作”来区分”。但是,如果/当需要支持新类型时,它们只需要进入此类(加上需要创建以支持新数据类型的任何其他类)。

class UnionData {
 public int NumericValue;
 public DateTime DateTimeValue;
}

有更好的选择吗?这两个选项中是否有我认为我不应该考虑的东西?

4

4 回答 4

3

方法 1,使用动态进行双重调度(信用转到http://blogs.msdn.com/b/curth/archive/2008/11/15/c-dynamic-and-multiple-dispatch.aspx)。基本上,您可以像这样简化访客模式:

class Evaluator {
 public void Evaluate(IEnumerable<ITimedValue> values) {
    foreach(var v in values)
    {
        Eval((dynamic)(v));
    }
 }

 private void Eval(DateTimeValue d) {
    Console.WriteLine(d.Value.ToString() + " is a datetime");
 }

 private void Eval(NumericValue f) {
    Console.WriteLine(f.Value.ToString() + " is a float");
 }

}

使用示例:

var l = new List<ITimedValue>(){
    new NumericValue(){Value= 5.1F}, 
    new DateTimeValue() {Value= DateTime.Now}};

new Evaluator()
    .Evaluate(l);
       // output:
       // 5,1 is a float
       // 29/02/2012 19:15:16 is a datetime

方法 2 将使用 @Juliet here提出的 c# 中的 Union 类型(此处的替代实现)

于 2012-02-29T18:20:06.430 回答
0

我告诉你我已经解决了类似的情况 - 是将TicksDateTime 或 TimeSpan 存储为集合中的 double 并使用 IComparable 作为类型参数的 where 约束。由辅助类执行到 double / from double 的转换。

请参阅上一个问题

有趣的是,这会导致其他问题,例如装箱和拆箱。我正在处理的应用程序需要极高的性能,因此我需要避免装箱。如果您能想到一种通用处理不同数据类型(包括 DateTime)的好方法,那么我全都听好了!

于 2012-02-29T16:41:32.787 回答
0

为什么不只实现你真正想要的接口,并允许实现类型定义值是什么?例如:

class NumericValue : ITimedValue<float> {
 public TimeSpan TimeStamp { get; private set; }
 public float Value { get; private set; }
}

class DateTimeValue : ITimedValue<DateTime>, ITimedValue<float> {
 public TimeSpan TimeStamp { get; private set; }
 public DateTime Value { get; private set; }
 public Float ITimedValue<Float>.Value { get { return 0; } }
}

class NumericEvaluator {
 public void Evaluate(IEnumerable<ITimedValue<float>> values) ...
}

如果您希望 DateTime 实现的行为根据特定用法(例如 Evaluate 函数的替代实现)而有所不同,那么根据定义,它们需要注意ITimedValue<DateTime>. 例如,您可以通过提供一个或多个Converter委托来获得良好的静态类型解决方案。

最后,如果您真的只想处理 NumericValue 实例,只需过滤掉任何不是 NumericValue 实例的内容:

class NumericEvaluator {
    public void Evaluate(IEnumerable<ITimedValue> values) {
        foreach (NumericValue value in values.OfType<NumericValue>()) {
            ....
        }
    }
}
于 2012-02-29T17:13:05.223 回答
0

好问题。我首先想到的是反射策略算法。运行时可以静态或动态地告诉您引用的派生最多的类型,而不管您用来保存引用的变量的类型。但是,不幸的是,它不会根据派生类型自动选择重载,只会根据变量类型选择重载。因此,我们需要在运行时询问真正的类型是什么,并在此基础上手动选择特定的重载。使用反射,我们可以动态地构建一个识别为处理特定子类型的方法集合,然后查询其泛型类型的引用,并基于此在字典中查找实现。

public interface ITimedValueEvaluator
{
   void Evaluate(ITimedValue value);
}

public interface ITimedValueEvaluator<T>:ITimedValueEvaluator
{
   void Evaluate(ITimedValue<T> value);
}

//each implementation is responsible for implementing both interfaces' methods,
//much like implementing IEnumerable<> requires implementing IEnumerable
class NumericEvaluator: ITimedValueEvaluator<int> ...

class DateTimeEvaluator: ITimedValueEvaluator<DateTime> ...

public class Evaluator
{
   private Dictionary<Type, ITimedValueEvaluator> Implementations;

   public Evaluator()
   {
      //find all implementations of ITimedValueEvaluator, instantiate one of each
      //and store in a Dictionary
      Implementations = (from t in Assembly.GetCurrentAssembly().GetTypes()
      where t.IsAssignableFrom(typeof(ITimedValueEvaluator<>)
      and !t.IsInterface
      select new KeyValuePair<Type, ITimedValueEvaluator>(t.GetGenericArguments()[0], (ITimedValueEvaluator)Activator.CreateInstance(t)))
      .ToDictionary(kvp=>kvp.Key, kvp=>kvp.Value);      
   }

   public void Evaluate(ITimedValue value)
   {
      //find the ITimedValue's true type's GTA, and look up the implementation
      var genType = value.GetType().GetGenericArguments()[0];

      //Since we're passing a reference to the base ITimedValue interface,
      //we will call the Evaluate overload from the base ITimedValueEvaluator interface,
      //and each implementation should cast value to the correct generic type.
      Implementations[genType].Evaluate(value);
   }   

   public void Evaluate(IEnumerable<ITimedValue> values)
   {
      foreach(var value in values) Evaluate(value);
   }
}

请注意,主 Evaluator 是唯一可以处理 IEnumerable 的 Evaluator;每个 ITimedValueEvaluator 实现应该一次处理一个值。如果这不可行(假设您需要考虑特定类型的所有值),那么这将变得非常容易;只需遍历 Dictionary 中的每个实现,将完整的 IEnumerable 传递给它,并让这些实现使用 OfType() Linq 方法将列表过滤为仅具有特定封闭泛型类型的对象。这将要求您运行您在列表中找到的所有 ITimedValueEvaluator 实现,如果列表中没有特定类型的项目,这是浪费精力。

它的美妙之处在于它的可扩展性。要支持 ITimedValue 的新通用闭包,只需添加相同类型的 ITimedValueEvaluator 的新实现。Evaluator 类将找到它,实例化一个副本并使用它。像大多数反射算法一样,它很慢,但实际的反射部分是一次性的。

于 2012-02-29T17:18:24.017 回答