Reputation: 845
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
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
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
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