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

Is it possible to specify a generic constraint for a type parameter to be convertible FROM another type?

假设我编写了一个包含以下内容的库:

1
2
3
4
5
6
7
8
9
10
11
12
public class Bar { /* ... */ }

public class SomeWeirdClass<T>
    where T : ???
{
    public T BarMaker(Bar b)
    {
        // ... play with b
        T t = (T)b
        return (T) b;
    }
}

稍后,我希望用户使用我的库来定义他们自己的类型,这些类型可以转换为条,并使用一些奇怪的类"工厂"。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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框架及其用户的独创性总是让我吃惊…

这可能与静态接口方法的想法有关,也可能与之无关——至少,我可以看到能够指定工厂方法来创建对象的价值(类似于您已经可以执行where T : new())。

编辑:解决方案-感谢Nick和Bzim-对于其他读者,我将根据自己的理解提供完整的解决方案:edit2:此解决方案要求foo公开公共默认构造函数。对于一个偶数stubiderbetter solution that does not required this see the very bottom of this post.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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:创建要使用的类型转换器工厂:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#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);
    }
}


听起来你找到了解决这个大问题的办法。要回答您的特定问题:不,C和CLR都不支持"向后"泛型类型参数约束。也就是说,

1
class C<T> where Foo : T

"t必须是foo或不支持foo转换为"的类型。

有些语言有这样的约束;IIRC scala就是这样一种语言。我怀疑这个特性对于反变体接口的某些使用是很方便的。


我认为在语言中不一定有一种在语法上很酷的方法可以做到这一点。解决问题的一个可能的方法是定义一个可转换的接口:

1
2
3
4
5
public interface IConvertible<T>
    where T :  new()   // Probably will need this
{
    T Convert();
}

那么你的班级可以是:

1
2
3
public class Foo : IConvertible<Bar>
{
}

我想这能让你接近你想去的地方…你的问题中所有的foo和bar有时会让你很难确定你的意图到底是什么。希望这有帮助。

编辑:添加了Where约束…您可能必须能够在可转换类中创建一个新实例。

编辑2:使foo继承ICovertible


与其经历定义接口和修改类来实现接口的麻烦,为什么不直接这样做呢?

1
2
3
4
5
6
7
8
9
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,可以直接向其强制转换Bar,则可以简单地调用此方法,如下所示:

1
2
3
var someWeirdObject = new SomeWeirdClass<Foo>();
var someBar = new Bar();
var someFoo = someWeirdObjcet.BarMaker(someBar, bar => bar as Foo);

顺便说一下(由于.NET 3.5中出现了Func委托),您也可以使用Converter作为converter参数(这完全相同)。


您可以通过用作类型约束的接口绕行。

例如,where T : IComparable用于将类型约束到可以与另一个对象进行比较的对象,后者必须通过实现IComparable来表示这种能力。如果您有一个接口ICastableFrom,您可以通过强制他们实现ICastableFrom来实现您想要的。