7

If I have a generic interface with a couple of implementing classes such as:

public interface IDataElement<T>
{
    int DataElement { get; set; }
    T Value { get; set; }
}

public class IntegerDataElement : IDataElement<int>
{
    public int DataElement { get; set; }
    public int Value { get; set; }
}

public class StringDataElement : IDataElement<String>
{
    public int DataElement { get; set; }
    public String Value { get; set; }
}

Is it possible to pass a collection of the implementing classes of differing types, without having to resort to passing as object.

It does not appear to be possible to define a return values as

public IDataElement<T>[] GetData()

or

public IDataElement<object>[] GetData() 

Any suggestions?

Joe
  • 46,419
  • 33
  • 155
  • 245
benPearce
  • 37,735
  • 14
  • 62
  • 96

3 Answers3

5

You can certainly declare:

public IDataElement<T>[] GetData<T>()

and

public IDataElement<object>[] GetData()
  • although the latter probably isn't what you're after (your interface won't be variant even in C# 4 as it uses T in both an input and an output position; even if it were variant, you wouldn't be able to use that variance for value types). The former will require the caller to specify <T>, e.g.

    foo.GetData<string>();

Is that okay for you?

There's no way of expressing "a collection of object, each of which implements IDataElement<T> for a different T" unless you also give it a nongeneric base class, at which you could just use IList<IDataElement>. In this case the nongeneric IDataElement could have the DataElement property, leaving the Value property in the generic interface:

public interface IDataElement
{
    int DataElement { get; set; }
}

public interface IDataElement<T> : IDataElement
{
    T Value { get; set; }
}

Is that useful in your particular situation?

It's not clear how you'd want to use a collection of data elements without knowing their types... if the above doesn't help you, maybe you could say more about what you expected to do with the collections.

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I had tried the non-generic base class but it loses the detail of the Value property – benPearce Jan 11 '10 at 21:40
  • @benPearce: But you can't use that anyway, because you don't know the type involved. Please try to show how you'd use the code, and then we can advise you better. – Jon Skeet Jan 11 '10 at 21:50
  • I ended up going down the Non-generic base interface, with object GetValue() and SetValue(object) methods. Working so far – benPearce Jan 15 '10 at 07:10
  • @JonSkeet, where would I be able to find a reason why covariance is not supported for structs? There is good reason, but can't come up with one for now :( – Erti-Chris Eelmaa Nov 10 '13 at 14:24
  • 1
    @Erti-ChrisEelmaa: See http://blogs.msdn.com/b/ericlippert/archive/2009/03/19/representation-and-identity.aspx and http://stackoverflow.com/a/12454932/22656 – Jon Skeet Nov 10 '13 at 16:38
3

No you can't do this - the only options are to either use a non-generic interface:

public interface IDataElement
{
    int DataElement { get; set; }
    object Value { get; set; }
}

Alternatively create a wrapper and pass that to methods that know the types they require:

public class DataElementBag
{
    private IDictionary<Type, List<object>> _elements;
    ...
    public void Add<T>(IDataElement<T> de)
    {
        Type t = typeof(T);
        if(!this._elements.ContainsKey(t))
        {
            this._elements[t] = new List<object>();
        }

        this._elements[t].Add(de);
    }

    public void IEnumerable<IDataElement<T>> GetElementsByType<T>()
    {
        Type t = typeof(T);
        return this._elements.ContainsKey(t)
            ? this._elements[t].Cast<IDataElement<T>>()
            : Enumerable.Empty<T>();
    }
}
Lee
  • 142,018
  • 20
  • 234
  • 287
0

Not sure if this may help your use case but you can a base / tag interface with nothing on it

public interface IDataElement {}

and have your Variant Generic Interface inherit from that (as Jon Skeet demonstrated)

public interface IDataElement<out T>

So you can then create a collection of these "objects" with its Contravariant, Non Variant Generic Interface (what a mouth full)

var collection = new List<IDataElement>(){ ... }

and when you take an element you may have to be a bit explicit I suppose but for me that is what C# Type safety is about (C# is very explicit!) and avoid using objects

IDataElement<MyType> value = List.First(x => DataElement == 12);

Or use the slightly frowned upon explicit cast

var element = (IDataElement<MyType>)List.First(x => DataElement == 12);

I was searching for this question because I had a generic method like TTYpe Get<TType>() because I was kind of messing with Strategy/Visitor pattern. I wanted to ask my provider for a arbitrary thing by using the generic Type as the parameter on the method.

Sounds odd maybe but I could actually just leverage Dependency Injection to inject any number of IDataElement implementations, that knew how to configure them selves based on the provider... all I wanted to do was like

var sqlConnection = tenantProvider.Get<SqlConnection>();
var storageTyep = tenantProvider<StorageProvider>();

And with the power of Generics, Variant Generic Interfaces and Tag Interfaces.. it works great. Strongly typed at the consumer, complete generic implementation.

 public TSetting GetSetting<TSetting>() where TSetting : class
 {
        var sp = tenantSettingsDictionary[typeof(TSetting)];
        return sp as TSetting;
 }
Piotr Kula
  • 9,597
  • 8
  • 59
  • 85