Marshall Conover
Marshall Conover

Reputation: 845

For C#, is it possible to specify type constraints in an instance declaration of a generic class?

I'd like to take two objects who have a shared hierarchy, and who both implement an interface, and put them both into one container, e.g. List, that normally takes a single type argument. I've used non-compiled code in this question, because I can't find a feature of C# that lets me do what I'm asking while compiling.

So, a setup such as:

public class DataObject
{
    int property;
}

public interface IWriteData
{
    void WriteData();
}

public class A : DataObject, IWriteData, IDoSomethingElseA
public class B : DataObject, IWriteData, IDoSomethingElseB

and code that does:

var x = new A();
var y = new B();
List<T> where T : (DataObject, IWriteData) mySharedContainer = new List<T>;
T.Add(x);
T.Add(y);

The List declaration line above doesn't work, but was the closest I could think of to get at what I'm aiming for. My hunch is that this isn't something currently able to be done, and that I need to either:

But, I'd be incredibly happy to find out otherwise. When thinking about it, I couldn't see any immediate reason that, theoretically, the compiler couldn't say at that line "okay, from now on I'll check everything added to this list has inherited from that class and implements these interfaces."

Thanks!

Upvotes: 2

Views: 2627

Answers (3)

Eric Lippert
Eric Lippert

Reputation: 660128

The feature you want is called intersection types, and C# has very, very limited support for intersection types. In fact there is only one way to specify a type restriction like that, and it is this:

class C 
{
    public static void M<T>(T t) where T : DataObject, IWriteData 
    {
        List<T> myList = new List<T>() { t };
        // With this restriction we can make both these conversions:
        IEnumerable<DataObject> iedo = myList; // Legal
        IEnumerable<IWriteData> iewd = myList; // Legal 
    }
}

T is restricted to be only types that are in the intersection of the types that implement IWriteData and the types that extend DataObject.

But this doesn't solve the problem that you have. In this solution we can call M<A> or M<B> and get a list of T where T is definitely both DataObject and IWriteData. But you have to say what T is. T can be A, or B, but T cannot be "either A or B". (And "either A or B" would be a union type.)

When thinking about it, I couldn't see any immediate reason that, theoretically, the compiler couldn't say at that line "okay, from now on I'll check everything added to this list has inherited from that objectclass and implements these interfaces."

(Objects are instances of classes; classes extend classes, not objects.)

You are correct; there is no theoretical reason. There are languages that support union and intersection types; Hack, the language that I work on now, is such a language. TypeScript also supports this kind of typing. But C# is not one of them, sorry.

Upvotes: 10

Dennis Kuypers
Dennis Kuypers

Reputation: 556

This is not possible.

In C# you can only specify one type for a field, method, property, etc. That is why generics can not do this. Generics just compile down to separate classes/methods for every type combination you use.

You have to implement the interface in the class or inherit interfaces so that you only have to specify only one interface.

Upvotes: 1

Nico
Nico

Reputation: 12683

I am not 100% sure what you are trying to achieve however the statement

List<T> where T : (DataObject, IWriteData) mySharedContainer = new List<T>;

Is not possible for a few reasons. First off we cant specify inline generics, and if we could the List<T> type does not accept two type arguments.

However as I see it you could effectivly create another interface say

public interface ISharedObject
{
    int Property { get; set; }

    void WriteData();
}

Then you simply modify your classes as:

public class A : DataObject, IWriteData, ISharedObject, IDoSomethingElseA
{
    public void WriteData();
}

public class B : DataObject, IWriteData, ISharedObject, IDoSomethingElseB
{
    public void WriteData();
}

So the interface ISharedObject has both the Property and WriteData() signatures. DataObject implements the Property (note i changed it to a property as opposed to a field). Then the interface IWriteData defines the contract for the WriteData method. Therefore both will compile.

Now you have a single type argument and can call

var x = new A();
var y = new B();
var list = new List<ISharedObject>();
list.Add(x);
list.Add(y);

Appologies in advance if I have misunderstood your requirement.

Upvotes: -1

Related Questions