假设我用以下内容编写了一个库:
public class Bar { /* ... */ }
public class SomeWeirdClass<T>
where T : ???
{
public T BarMaker(Bar b)
{
// ... play with b
T t = (T)b
return (T) b;
}
}
稍后,我希望用户通过定义自己的类型来使用我的库,这些类型可以转换为Bar并使用SomeWeirdClass“factory”。
public class Foo
{
public static explicit operator Foo(Bar f)
{
return new Bar();
}
}
public class Demo
{
public static void demo()
{
Bar b = new Bar();
SomeWeirdClass<Foo> weird = new SomeWeirdClass<Foo>();
Foo f = weird.BarMaker(b);
}
}
如果我设置where T : Foo
,这将编译,但问题是我在库编译时不知道Foo,我实际上想要更像where T : some class that can be instantiated, given a Bar
这可能吗?从我有限的知识来看,它似乎并不存在,但.NET框架及其用户的独创性总让我惊喜......
这可能与static interface methods的概念有关或不相关 - 至少,我可以看到能够指定工厂方法的存在以创建对象的价值(类似于你已经可以的方式执行where T : new()
)
编辑:解决方案 - 感谢Nick和bzIm - 对于其他读者,我将提供一个完整的解决方案,据我所知:
edit2:此解决方案要求Foo公开公共默认构造函数。对于一个偶然的 stupider 更好的解决方案,不需要这个就看到这个帖子的最底层。
public class Bar {}
public class SomeWeirdClass<T>
where T : IConvertibleFromBar<T>, new()
{
public T BarMaker(Bar b)
{
T t = new T();
t.Convert(b);
return t;
}
}
public interface IConvertibleFromBar<T>
{
T Convert(Bar b);
}
public class Foo : IConvertibleFromBar<Foo>
{
public static explicit operator Foo(Bar f)
{
return null;
}
public Foo Convert(Bar b)
{
return (Foo) b;
}
}
public class Demo
{
public static void demo()
{
Bar b = new Bar();
SomeWeirdClass<Foo> weird = new SomeWeirdClass<Foo>();
Foo f = weird.BarMaker(b);
}
}
edit2:解决方案2 :创建要使用的类型转换器工厂:
#region library defined code
public class Bar {}
public class SomeWeirdClass<T, TFactory>
where TFactory : IConvertorFactory<Bar, T>, new()
{
private static TFactory convertor = new TFactory();
public T BarMaker(Bar b)
{
return convertor.Convert(b);
}
}
public interface IConvertorFactory<TFrom, TTo>
{
TTo Convert(TFrom from);
}
#endregion
#region user defined code
public class BarToFooConvertor : IConvertorFactory<Bar, Foo>
{
public Foo Convert(Bar from)
{
return (Foo) from;
}
}
public class Foo
{
public Foo(int a) {}
public static explicit operator Foo(Bar f)
{
return null;
}
public Foo Convert(Bar b)
{
return (Foo) b;
}
}
#endregion
public class Demo
{
public static void demo()
{
Bar b = new Bar();
SomeWeirdClass<Foo, BarToFooConvertor> weird = new SomeWeirdClass<Foo, BarToFooConvertor>();
Foo f = weird.BarMaker(b);
}
}
答案 0 :(得分:5)
听起来你找到了解决这个更大问题的方法。回答您的具体问题:不,C#和CLR都不支持“向后”泛型类型参数约束。也就是说,
class C<T> where Foo : T
“T必须是Foo或Foo转换为的类型”不受支持。
有些语言有这种约束; IIRC Scala就是这样一种语言。我怀疑这个功能对于逆变接口的某些用途会很方便。
答案 1 :(得分:4)
我认为在语言中内置这种语法并不一定有一种语法上很酷的方法。解决问题的一种可能方法是定义可转换接口:
public interface IConvertible<T>
where T : new() // Probably will need this
{
T Convert();
}
然后你的班级可能是:
public class Foo : IConvertible<Bar>
{
}
我认为这会让你接近你想要的地方......在你的问题中所有的Foo和Bar都有时难以确定你的意图。希望这会有所帮助。
编辑:添加了约束...您可能必须能够在可转换类中创建新实例。
编辑2: Made Foo继承自ICovertible<Bar>
答案 2 :(得分:1)
您可以通过用作类型约束的接口绕道而行。
例如,where T : IComparable<U>
用于将类型约束为可以与另一个事物进行比较的事物,这必须通过实施IComparable<another>
来表达这种能力。如果您有一个界面ICastableFrom<T>
,您可以通过强制实施ICastableFrom<Bar>
来实现您想要的目标。
答案 3 :(得分:1)
为什么不这样做,而不是经历定义接口和修改类来实现该接口的麻烦,为什么不这样做呢?
public class SomeWeirdClass<T>
{
// aside: why is this method called 'BarMaker' if it returns a T?
public T BarMaker(Bar b, Func<Bar, T> converter)
{
// ... play with b
return converter(b);
}
}
然后,如果您正在处理可以直接转换T
的{{1}}类型的对象,则可以按如下方式调用此方法:
Bar
顺便说一下(因为{3.5}中出现了var someWeirdObject = new SomeWeirdClass<Foo>();
var someBar = new Bar();
var someFoo = someWeirdObjcet.BarMaker(someBar, bar => bar as Foo);
委托),你也可以使用Func<T, TResult>
(这完全相同)Converter<TInput, TOutput>
参数。