Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

generics

Suppose I write a library with the following:

public class Bar { /* ... */ }

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

Later, I expect users to use my library by defining their own types which are convertible to Bar and using the 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);
    }
}

this will compile if i set where T : Foo but the problem is that I don't know about Foo at the library's compile time, and I actually want something more like where T : some class that can be instantiated, given a Bar

Is this possible? From my limited knowledge it does not seem to be, but the ingenuity of the .NET framework and its users always surprises me...

This may or not be related to the idea of static interface methods - at least, I can see the value in being able to specify the presence of factory methods to create objects (similar to the same way that you can already perform where T : new())

edit: Solution - thanks to Nick and bzIm - For other readers I'll provide a completed solution as I understand it: edit2: This solution requires Foo to expose a public default constructor. For an even stupider better solution that does not require this see the very bottom of this post.

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: Solution 2: Create a type convertor factory to use:

#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);
    }
}
like image 764
fostandy Avatar asked Mar 04 '10 19:03

fostandy


4 Answers

Sounds like you found a solution to the larger problem. To answer your specific question: no, neither C# nor the CLR support the "backwards" generic type parameter constraint. That is,

class C<T> where Foo : T

"T must be Foo or a type which Foo converts to" is not supported.

There are languages that have that sort of constraint; IIRC Scala is such a language. I suspect this feature would be handy for certain uses of contravariant interfaces.

like image 181
Eric Lippert Avatar answered Nov 16 '22 14:11

Eric Lippert


I don't think there is necessarily a syntactically cool way to do this built into the language. One possible solution to your problem could be to define a convertible interface:

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

Then your class could be:

public class Foo : IConvertible<Bar>
{
}

I think this gets you close to where you want to be... All the Foo's and Bar's in your question sometimes make it hard to determine exactly what your intent is. Hope this helps.

Edit: Added where constraint... you will probably have to be able to create a new instance in your convertible class.

Edit 2: Made Foo inherit from ICovertible<Bar>

like image 43
Nick Avatar answered Nov 16 '22 13:11

Nick


You could make a detour via an interface which is used as a type constraint.

For example, where T : IComparable<U> is used to constrain the type to something that can be compared to another thing, which must express this ability by implementing IComparable<another>. If you had an interface ICastableFrom<T>, you could achieve what you want by forcing them to implement ICastableFrom<Bar>.

like image 1
bzlm Avatar answered Nov 16 '22 14:11

bzlm


Rather than go through the trouble of defining an interface and modifying your class to implement that interface, why not just do this?

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

Then in the event that you are dealing with an object of a type T to which Bar can be directly cast, this method could be called simply as follows:

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

By the way (since the Func<T, TResult> delegate emerged in .NET 3.5), you could also use Converter<TInput, TOutput> (which is exactly the same) for the converter parameter.

like image 1
Dan Tao Avatar answered Nov 16 '22 13:11

Dan Tao