代码之家  ›  专栏  ›  技术社区  ›  fostandy

是否可以为要从其他类型转换的类型参数指定泛型约束?

  •  8
  • fostandy  · 技术社区  · 14 年前

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

    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公开公共默认构造函数。为了一个偶数 小精灵 更好的解决方案,不需要这一点看到这篇文章的最底部。

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

    编辑2: 解决方案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 回复  |  直到 14 年前
        1
  •  4
  •   Nick    14 年前

    我不认为一定有一种语法上很酷的方法来实现这一点。一个可能的解决方案是定义一个可转换接口:

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

    那么你的班级可能是:

    public class Foo : IConvertible<Bar>
    {
    }
    

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

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

    编辑2: 使foo继承自 ICovertible<Bar>

        2
  •  4
  •   Eric Lippert    14 年前

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

    class C<T> where Foo : T
    

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

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

        3
  •  1
  •   bzlm    14 年前

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

    例如, where T : IComparable<U> 用于将类型约束为可以与另一个对象进行比较的对象,该对象必须通过实现 IComparable<another> . 如果你有一个接口 ICastableFrom<T> ,通过强制他们实现 ICastableFrom<Bar> .

        4
  •  1
  •   Dan Tao    14 年前

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

    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 可以直接转换,此方法可以简单地调用如下:

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

    顺便说一下(自从 Func<T, TResult> 委托出现在.NET3.5中),您也可以使用 Converter<TInput, TOutput> (完全相同)对于 converter 参数.