0

I have following code that does not compile

using System.Collections.Generic;

public interface IElement
{
}

public class AElement : IElement
{
    public void DoSomethingSpecial()
    { }
}

public class Container<TElement>
{
    public List<TElement> Elements { get; } = new();
}



public class Program
{
    public static Container<IElement> GetContainer()
    {
        var concreteContainer = new Container<AElement>();
        concreteContainer.Elements.ForEach(e => e.DoSomethingSpecial());
        
        return concreteContainer; // Cannot implicitly convert type 'Container<AElement>' to 'Container<IElement>'
    }
    
    public static void Main()
    {
        var myContainer = GetContainer();
    }
}

I read documentation about Covariance, Invariance, Contravariance and out Types. And I am more confused than at the beginning.

Whats the way to fix this?

Code online: https://dotnetfiddle.net/85AgfT

Klamsi
  • 846
  • 5
  • 16
  • `AElement` "is-a" `IElement`. But that doesn't make `Container` "is-a" `Container`. They have no such relation. – Fildor Aug 27 '21 at 07:37
  • 2
    For _why_ this happens, see [this](https://stackoverflow.com/questions/3720751/casting-list-of-derived-class-to-list-of-base-class). Once you understand that, you might want to think again about what you want to do. – Sweeper Aug 27 '21 at 07:38
  • @Sweeper: An answer "Don't do it like this" is also ok. But isn't it common that somebody wants to have an interface but internally you have to work with concrete types (first)? Because every concrete implementation needs custom behaviour? – Klamsi Aug 27 '21 at 07:42
  • @Klamsi That's no contradiction. If you have a list, say `List` where `IElement` has `MethodA()`, you can add any concrete class to the list that implements that interface. Any of those can have their individual implementations of `MethodA()`. So, by calling `foreach( var item in list ) item.MethodA();` the corresponding implementation will be executed. – Fildor Aug 27 '21 at 08:02
  • 1
    I'll lean back and rethink my concept. – Klamsi Aug 27 '21 at 08:06
  • Changed the concept and it works fine. There are exactly two places where I need the concrete type. With `(interfaceType as concreteType).DoSomethingSpecial()` it works fine. – Klamsi Aug 27 '21 at 08:37
  • @Fildor: It gets from bad to worse. I thought I had a solution but (of course) JsonConvert.DeserializeObject cannot create a `Container` and stops with a runtime error. So stuck again. Interfaces are cool - until you use them. – Klamsi Aug 27 '21 at 14:06
  • You may need some extra work for that to work out, yes. See: https://stackoverflow.com/a/28133806/982149 – Fildor Aug 27 '21 at 14:18

2 Answers2

1

You need to generate implicit conversion operator:

public class Container<IElement>
{
    public List<IElement> Elements { get; } = new List<IElement>();

    public static implicit operator Container<IElement>(Container<AElement> v)
    {
        //here you need to create Container<IElement> with your Container<AElement> 'v' values
        return new Container<IElement>();
    }
}
ElConrado
  • 1,477
  • 4
  • 20
  • 46
  • Oh no. In fact this container is a bigger hierarchical structure. I was hoping for a one liner ;) – Klamsi Aug 27 '21 at 07:40
  • 1
    @Klamsi Nope, that's not it. I even think you need to go back to the blackboard over your design ... :( – Fildor Aug 27 '21 at 07:41
0

I finally got it working

using System.Collections.Generic;

public interface IContainer<out TElement>
{
}

public interface IElement
{
}

public class AElement : IElement
{
    public void DoSomethingSpecial()
    { }
}

public class Container<TElement> : IContainer<TElement>
{
    public List<TElement> Elements { get; } = new();
}



public class Program
{
    public static IContainer<IElement> GetContainer()
    {
        var concreteContainer = new Container<AElement>();
        concreteContainer.Elements.ForEach(e => e.DoSomethingSpecial());
        
        return concreteContainer;
    }
    
    public static void Main()
    {
        var myContainer = GetContainer();
    }
}

Make Container also an Interface and use an out Type parameter

Klamsi
  • 846
  • 5
  • 16