Search code examples
c#genericsinterfacecastingienumerable

How do I add a derived object to a collection of objects which inherit from the same interface, but use generics?


I'm somewhat new to generics in C#. I have a IResult interface which has the following properties:

    public interface IResult<T>
    {
         string ResultMessage { get; set; }
         T Data { get; set; }
    }

The purpose being that inheriting objects will be returning a string message with a payload to the UI. This will be delivered via another object which has detailed information and handles serialize/deserialization, which will contain an IEnumerable of these objects. However, it needs to hold any IResult, as there may be multiple types in the one object. It must also only hold IResults in that collection. This is the property which is intended to contain them:

public IEnumerable<IResult<dynamic>> Results { get; set; }

The problem is, casting with IEnumerable.Cast() or an explicit inline cast doesn't work. The presence of dynamic here was just an attempt by me after doing the same thing with object and the cast failing then as well.

How can I have my larger payload object accept a collection of IResult<T>, since none of these work (the first because T is undefined in the context?)

IEnumerable<IResult<T>> Results { get; set; } 
//doesn't work as T is undefined, and if defined at the payload level, would result in a single type
IEnumerable<IResult<dynamic>> Results { get; set; } 
//doesn't work, the cast fails
IEnumerable<IResult<object>> Results { get; set; } 
//doesnt work, the cast fails

Solution

  • What you want to achieve is not related to generics in any case. It is important to understand that by default generics are invariant, e.g.:

    IInterface<One> != IInterface<Two>
    

    even if Two: One.

    You can read more about it here

    One of the simplest ways to achieve what you want is to use objects

    public interface IResult
    {
         string ResultMessage { get; set; }
         object Data { get; set; }
    }
    

    If, however, you want to stick to generics, you can define your IResult interface as covariant:

    public interface IResult<out T>
    {
        string ResultMessage { get; set; }
        T Data { get; } // note that you cannot specify setter in this case
    }
    

    Then, define your Result classes like this:

    public class ObjectResult: IResult<object>
    {
        private readonly object _data;
    
        public ObjectResult(object data)
        {
            _data = data;
        }
    
        public string ResultMessage { get; set; }
        public object Data => _data;
    }
    
    public class StringResult : IResult<string>
    {
        private readonly string _data;
    
        public StringResult(string data)
        {
            _data = data;
        }
    
        public string ResultMessage { get; set; }
        public string Data => _data;
    }
    

    Now you're able to go:

    IResult<string> stringResult = new StringResult("string");
    IResult<object> objectResult = new ObjectResult(new object());
    
    objectResult = stringResult;
    
    Console.WriteLine(objectResult.Data); // prints "string"