是否可以为类型参数指定一个通用约束,以便从另一个类型转换?

时间:2010-03-04 19:21:58

标签: c# generics

假设我用以下内容编写了一个库:

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);
    }
}

4 个答案:

答案 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>参数。