我试图找到一种更好的方法来处理一些不断增长的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) ...
}
我提出了两个选择:
Double Dispatch
我最近了解到访问者模式及其使用双重调度来处理这种情况。这很有吸引力,因为它会允许不需要的数据不传播(如果我们只想处理一个int,我们可以处理与DateTime不同的方式)。此外,处理不同类型的行为将局限于处理调度的单个类。但是,如果/必须支持新的值类型,则需要进行相当多的维护。
联盟班级
包含所支持的每种值类型的属性的类可以是每个类存储的内容。对值的任何操作都会影响相应的组件。这比双调度策略更简单,维护更少,但这意味着每一段数据都会不必要地传播,因为你不能再按照“我不对该数据类型进行操作”的方式进行区分。 ”。但是,如果/当需要支持新类型时,它们只需要进入这个类(加上需要创建的任何其他类来支持新数据类型)。
class UnionData {
public int NumericValue;
public DateTime DateTimeValue;
}
有更好的选择吗?这两个选项中的任何一个都有我认为不应该的东西吗?
答案 0 :(得分: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
答案 1 :(得分:0)
我告诉你我已经解决了类似的情况 - 将DateTime或TimeSpan的Ticks
存储为集合中的double,并使用IComparable作为type参数的where约束。双精度/双精度转换由辅助类执行。
有趣的是,这导致了其他问题,例如装箱和拆箱。我正在处理的应用程序需要极高的性能,所以我需要避免装箱。如果您能想到一般处理不同数据类型(包括DateTime)的好方法,那么我全都耳朵!
答案 2 :(得分: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>()) {
....
}
}
}
答案 3 :(得分: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的;每个ITimedValueEvaluator实现应该一次处理一个值。如果这不可行(假设您需要考虑特定类型的所有值),那么这变得非常简单;只需循环遍历Dictionary中的每个实现,向其传递完整的IEnumerable,并让这些实现使用OfType()Linq方法将列表过滤为特定的封闭泛型类型的对象。这将要求您运行列表中找到的所有ITimedValueEvaluator实现,如果列表中没有特定类型的项目,则会浪费精力。
这就是它的可扩展性;要支持ITimedValue的新通用关闭,只需添加相同类型的ITimedValueEvaluator的新实现。 Evaluator类将找到它,实例化一个副本并使用它。像大多数反射算法一样,它很慢,但实际反射部分是一次性交易。