背景
我正在着手开发一个名为Sieve.NET.的OSS库
签名允许某人定义Sieve
如下:
new EqualitySieve<ABusinessObject>().ForProperty(x => x.AnInt);
这实际上返回了一个Sieve<ABusinessObject, int>
,但我已经尽了最大努力来确保用户不必太关心这一部分。
任务
我想找到一种方法在上面放置一个接口,在那里我根本不关心属性类型——只关心它在整个过程中是一致的。
因此,从本质上讲,我希望能够声明一个ISieve<TFilterObjectType>
,并通过能够让该接口定义如下内容:
ISieve<TFilterObjectType, TTypeIDontCareAbout> ForValue(TTypeIDontCareAbout);
我的目标是能够有一个由ISieve<ABusinessObject>
而不是ISieve<ABusinessObject, int>
组成的类。
问题
- 有没有一种方法可以让接口声明一个有效的通配符类型,并说"我不在乎这是什么类型,只在乎它是一致的?">
我最初的研究表明没有,但我希望被证明是错误的。
更新&澄清
我真正想弄清楚的是:
- 我允许用户创建
EqualitySieve<ABusinessObject>().ForProperty(x=>x.AnInt)
- 这实际上向用户返回了一个
EqualitySieve<ABusinessObject, int>
,但由于这是一个流畅的界面,我使他们不必关心那个部分 - 我希望
EqualitySieve
、LessThanSieve
等实现ISieve<ABusinessObject>
- 我希望
ISieve<ABusinessObject
强制执行一个合同,这样我就可以允许某人调用ForValues()
,并期望它返回一个带有更新值的ISieve - 然而,在这一点上,
EqualitySieve<ABusinessObject>
实际上是EqualitySieve<ABusinessObject, int>
。但在这一点上,我并不特别关心房产类型 - 从本质上讲,由于我将
EqualitySieve<ABusinessObject, int>
部分抽象掉,所以我也想看看在通过接口引用对象时是否可以将其抽象掉 - 长期计划是,我想要一个SieveLocator,在那里类可以实现一个理想情况下返回
ISieve<ABusinessObject>
的IFindableSieve<ABusinessObject>
。然后我的目标是能够为给定的对象找到这些Sieves - 所以我认为这可能是我设计的局限性,我必须找到其他方法来解决它。任何关于这一点的建议或对我可能看不到的模式的引用都会很有帮助
您可以在接口和接口的方法上放置泛型类型参数。因此,下面的例子将定义一个通用接口,其中F
方法接受其中一个"我不在乎这是什么类型,只在乎它是一致的"参数。
interface I<T>
{
//The generic type parameter U is independent of T.
//Notice how F "forwards" the type U from input to output.
Tuple<T, U> F<U>(U u);
}
考虑以下玩具类:
class C : I<char>
{
public char Value { get; set; }
public Tuple<char, U> F<U>(U u)
{
return Tuple.Create(Value, u);
}
}
以下是一些用法示例:
I<char> instance = new C { Value = '!' };
Tuple<char, int> x = instance.F(5); // ('!', 5)
Tuple<char, string> y = instance.F("apple"); // ('!', "apple")
更新
- 我允许用户创建
EqualitySieve<ABusinessObject>().ForProperty(x=>x.AnInt)
- 这实际上向用户返回了一个
EqualitySieve<ABusinessObject, int>
,但由于这是一个流畅的界面,我使他们不必关心那个部分- 我希望
EqualitySieve
、LessThanSieve
等实现ISieve<ABusinessObject>
利用我上面提到的想法,你可以做你想做的事。
interface ISieve<T>
{
//It's still not clear what you actually want in this interface...
}
static class Sieve
{
public EqualitySieve<T> Equality<T>()
{
return new EqualitySieve<T>();
}
public LessThanSieve<T> LessThan<T>()
{
...
}
}
class EqualitySieve<T> : ISieve<T>
{
//Notice how the property type P is independent of T
//and can be inferred here from the passed expression
public EqualitySieve<T, P> ForProperty<P>(
Expression<Func<T, P>> propertyExpression)
{
return new EqualitySieve<T, P>
{
PropertyExpression = propertyExpression
};
}
}
class EqualitySieve<T, P> : ISieve<T>
{
public Expression<Func<T, P>> PropertyExpression { get; set; }
}
用法:
//Assuming MyObject.MyProperty is an int property
//s has type EqualitySieve<MyObject, int>
var s = Sieve.Equality<MyObject>().ForProperty(x => x.MyProperty);
可能有一些技巧,所以调用者不需要在泛型方法上指定类型(想想LINQ是如何工作的(,但不幸的是,您的研究是正确的,在编写使用该类型的类时无法推断类型。
最接近它的是有两层接口,其中外层不使用任何依赖于TTypeIDontCareAbout
类型的函数。
interface ISieve<TFilterObjectType,TTypeIDontCareAbout> : ISieve<TFilterObjectType>
{
TFilterObjectType ForValue(TTypeIDontCareAbout forValue);
}
interface ISieve<TFilterObjectType>
{
TFilterObjectType SomeOtherFunction();
}
我不知道如何解决你所有的问题,但我认为Timothy的方法是你想要的两点
- 我允许用户创建一个
EqualitySieve<ABusinessObject>().ForProperty(x=>x.AnInt)
- 这实际上向用户返回了一个
EqualitySieve<ABusinessObject, int>
,但由于这是一个流畅的界面,我使他们不必关心那个部分
interface ISieve<TFilterObjectType>
{
TFilterObjectType SomeOtherFunction();
EqualitySieve<TFilterObjectType, T> ForProperty<T>(Func<TFilterObjectType, T> selector);
EqualitySieve<TFilterObjectType, T> ForProperty<T>(Expression<Func<TFilterObjectType, T>> selector); //This is how you would do it if you wanted IQueryable support.
}