Search code examples
c#genericscovariance

Multiple classes that implement a generic interface in a single array


Say I have a generic interface that stores a value that is typed by the generic parameter:

public interface IFoo<TValue>
{
    TValue SomeValue { get; set; }
}

Then I have two classes, StringFoo and DoubleFoo whose SomeValues are strings and doubles respectively:

public class StringFoo : IFoo<string>
{
    string SomeValue { get; set; }
}

public class DoubleFoo : IFoo<double>
{
    double SomeValue { get; set; }
}

I now decide that I want an array that can contain both StringFoos and DoubleFoos:

var foos = IFoo<object>[] {
    new StringFoo { SomeValue = "a value" },
    new DoubleFoo { SomeValue = 123456789 }
}

I would think that, since both string and double are subclasses of object, they would both be allowed in this array. However, I thought wrong.

So, I tried using covariance:

public interface IFoo<out TValue>

But, since the interface contains both setters and getters, I can't do this.

So, can two classes that implement a generic interface in one array?


Solution

  • The problem can be solved in the way Bar class is implemented (bad example provided as well). The issue is that whenever one tries to use generic interface or class implementing generic interface code may compile (providing right casts) but code will throw InvalidCastException during runtime.

    public interface IFoo<TValue>
    {
        TValue SomeValue { get; set; }
    }
    
    public class StringFoo : IFoo<string>
    {
        public string SomeValue { get; set; }
    }
    
    public class DoubleFoo : IFoo<double>
    {
        public double SomeValue { get; set; }
    }
    
    public class Foo<TValue> : IFoo<TValue>
    {
        public TValue SomeValue { get; set; }
    }
    
    public abstract class Bar
    {
    
    }
    
    public class Bar<TValue> : Bar, IFoo<TValue>
    {
        public TValue SomeValue { get; set; }
    }
    
    public static class Verify
    {
        public static void QuestionArray()
        {
            var foos = new IFoo<object>[]
            {
                (IFoo<object>) new StringFoo { SomeValue = "a value" },
                (IFoo<object>) new DoubleFoo { SomeValue = 123456789 }
            };
        }
    
        public static void BadAnswerArray()
        {
            var foo = new IFoo<object>[]
            {
                (IFoo<object>) new Foo<string>(),
                new Foo<object>(),
            };
        }
    
        public static void GoodAnswer()
        {
            var foo = new Bar[]
             {
                new Bar<string>(),
                new Bar<object>(),
                new Bar<double>()
             };
        }
    }
    

    And to verify solution one can run tests where only GoodAnswerTest will pass:

    public class GenericsInArrayTests
    {
        [Fact]
        public void QuestionArrayTest()
        {
            Verify.QuestionArray();
        }
    
        [Fact]
        public void BadAnswerTest()
        {
            Verify.BadAnswerArray();
        }
    
        [Fact]
        public void GoodAnswerTest()
        {
            Verify.GoodAnswer();
        }
    }