假设我有一个通用的List<ICalculation>
,它可以作为我的应用程序中所有预定义计算的存储库......
我有一个名为ICalculation<T, U>
的通用接口,它实现了更基本的ICalculation
。
public interface ICalculation
{
string Identifier { get; }
object Calculate(object inputData);
}
public interface ICalculation<in TIn, out TOut> : ICalculation
{
string Identifier { get; }
TOut Calculate(TIn inputData)
}
我还有一个实现此接口的抽象类CalculationBase
public abstract class CalculationBase<TIn, TOut> : ICalculation<in TIn, out TOut>, ICalculation
{
public abstract string Identifier { get; }
public abstract Func<TIn, TOut> Calculation { get; }
public virtual TOut Calculate(TIn inputData)
{
return Calculate(inputData, Calculation);
}
virtual object ICalculation.Calculate(object inputData)
{
return (TOut)calculation((TIn)inputData);
}
public static TOut Calculate(TIn inputData, Func<TIn, TOut> calculation)
{
if (calculation == null || inputData == null)
return default(TOut);
return calculation(inputData);
}
}
所以,现在我有一大堆计算实现了在某些输入上起作用的CalculationBase ......一个例子:
public sealed class NumberOfBillableInvoices : CalculationBase<IClientAccount, int>
{
public override string identifier { get { return "@BillableInvoiceCount"; } }
public override Func<IClientAccount, int> Calculation
{
get { return inputData => inputData.Invoices.Count(i => i.IsBillable); }
}
}
每个计算都针对特定类型的对象,并根据计算的性质返回不同的输出。例如:货币计算可能会返回小数,计数器可能会返回整数或长整数等。
我有一个计算存储库,可以在应用程序加载时自行加载,当有时间必须计算公式时,计算引擎将获取正在查询的对象 - 在此示例中,如果我们有一些具体的实例类型为IClientAccount
,我们希望根据它来评估一些公式 - 例如,在前5个"Math.Max(@BillableInvoiceCount - 5, 0) * $1.20"
之后为每张发票征收1.20美元。引擎进行并抓取所有计算,其中TIn的类型为IClientAccount,并将计算与公式中找到的令牌(即@BillableInvoiceCount
)相匹配。然后一些计算引擎,如NCalc,FLEE或其他计算引擎将评估最终的等式。
所以,我的问题是我不希望迭代每个计算寻找正确的令牌 - 实际上,如果令牌跨越多个对象类型,它们可能会发生冲突。例如,我可能想要使用相同的标记来表示不同上下文中的不同内容。如果我可以将我的存储库中的计算范围缩小到那些TIn匹配我想要计算的对象类型的那些会更容易。
此时我有几点思路 -
1)。我可以创建一个只对我的对象的TIn部分进行编组的存储库吗?我认为这个问题的答案很可能,不是......但是考虑到它的可能性,我没有第一个线索如何实现这一点 - 有没有人有任何想法?
2)。有没有办法查询我的存储库中所有的计算,其中TIn匹配我正在查询的对象的类型?如果是这样,怎么样?
3)。我是否有多个存储库基于我计算的TIn / TOut的所有组合...如果是这样,我如何将正确的存储库与我要查询的对象结合起来?因为我还在尝试仅基于TIn部分匹配存储库...
4)。让我的所有计算返回双精度而不是允许它们返回不同的类型,然后我的存储库可以输入到输入类型,使它们更简单......但是这很简单,从语义上讲,它只是感觉不对。
思想?
提前干杯:)
答案 0 :(得分:1)
如果您知道所有内容都来自CalculationBase<,>
,我想您可以这样做:
// can also be made an extension method
static bool HasCorrectInType(ICalculation calc, Type desiredInType)
{
var t = calc.GetType();
do
{
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(CalculationBase<,>))
return t.GetGenericArguments()[0].IsAssignableFrom(desiredInType);
t = t.BaseType;
} while (t != null)
throw new Exception("The type " + calc.GetType() + " not supported");
}
然后像这样使用它:
List<ICalculation> repository = XXX;
var matches = repository.Where(c => HasCorrectInType(c, type));
编辑:新想法:如果您添加了新属性:
public Type InType
{
get { return typeof(TIn); }
}
进入抽象类CalculationBase<TIn, TOut>
,并将此属性添加到非泛型接口ICalculation
,然后您不必遍历基类,但可以说{{1}直接。
答案 1 :(得分:1)
请记住,泛型是一个编译时工件,在编写时,如果要使用它们,则需要知道所需的类。如果您需要运行时检查,那么非通用方式可能是最好的。
假设您知道对象的类型正确 - &gt;对象重载应该适合您的目的。如果映射失败,.NET框架将抛出异常,因此您不必担心那里的静默失败。
1)。我可以创建一个只对我的对象的TIn部分进行编组的存储库吗?我认为这个问题的答案很可能,不是......但是考虑到它的可能性,我没有第一个线索如何实现这一点 - 有没有人有任何想法?
编组指的是基础数据的转换,在这种情况下您可能只是简单地传递原始对象,因为您的ICalculation.Calculate
方法会为您进行转换。您可能遇到的唯一问题是,如果TIn
是值类型并且您传入null(在这种情况下,您的Calculate
null处理将不会发生)
2)。有没有办法查询我的存储库中所有的计算,其中TIn匹配我正在查询的对象的类型?如果是这样,怎么样?
我会尝试使用非通用版本,除非你想要更清晰的异常树,它应该在这种情况下完成工作。
3)。我是否有多个存储库基于我计算的TIn / TOut的所有组合...如果是这样,我如何将正确的存储库与我要查询的对象结合起来?因为我还在尝试仅基于TIn部分匹配存储库...
如果你想这样做,那就是让你的保存方法只保存TIn
而不是两者。例如,Dictionary<Type,ICalculation>
Type
为TIn
的{{1}}。
4)。让我的所有计算返回双精度而不是允许它们返回不同的类型,然后我的存储库可以输入到输入类型,使它们更简单......但是这很简单,从语义上讲,它只是感觉不对。
有一点需要注意,我的建议只有在方法调用之间没有进行任何转换时才有效。如果int
中有object
,并且您尝试将其转换为double
,则会失败。
您可以通过调用Convert.ChangeType
而不是直接投射来避免这种情况。它会像这样工作:
object ICalculation.Calculate(object inputData)
{
if (inputData == null && typeof(TIn).IsValueType)
return default(TOut);
return Calculate((TIn)Convert.ChangeType(inputData, typeof(TIn));
}
请注意该方法的一些更改:
null
处理程序,因为Convert.ChangeType
只会抛出异常。Calculate
的通用格式。calculation
和Calculate
都保证返回TOut
,因此转换是多余的。ChangeType
,可让您静默处理将int
传递到decimal
。请注意,ChangeType
存在危险,它类似于显式转换。无论您的数据发生什么变化,它都会尽力进行转换。似乎溢出将按预期处理,但截断将以静默方式发生。
如果您有任何类似的话,那么主要的一点是测试你的边缘情况。
答案 2 :(得分:0)
我认为应该选择最简单的解决方案。您可以使用反射来获取特定类型,或者只在所有情况下返回double。如果是密集数据处理,尝试查找特定类型可能会降低速度,因此返回double或整数是完全正常的。