TL;DR: If List<T> implements ICollection<T>, and Facility implements IAmAHierarchyNode, why can't I implicitly convert List<Facility> to ICollection<IAmAHierarchyNode>?
I'm getting an error of "Cannot implicitly convert type 'Foo' to 'Bar'. An explicit conversion exists (are you missing a cast?)"
Here's the code:
public interface IAmAHierarchyNode<IdType> {
      IdType Id {
         get;set;
      }
      ICollection<IAmAHierarchyNode<IdType>> Children {
         get;
      }
      IAmAHierarchyNode<IdType> Parent {
         get;set;
      }
}
public class Network : IAmAHierarchyNode<Guid> {
   public List<Facility> Facilities
   {
      get;set;
   }
   // This is where it's making me do an explicit conversion. But why?
   public ICollection<IAmAHierarchyNode<Guid>> Children => Facilities;
   public IAmAHierarchyNode<Guid> Parent {
      get => throw new InvalidOperationException ();
      set => throw new InvalidOperationException ();
   }
}
public class Facility : IAmAHierarchyNode<Guid> {
   public Network Network {
      get;set;
   }
   public Guid NetworkId {
      get;set;
   }
   public ICollection<IAmAHierarchyNode<Guid>> Children {
      get => throw new NotImplementedException ();
   }
   public IAmAHierarchyNode<Guid> Parent {
      get => Network;
      set => throw new NotImplementedException ();
   }
}
Here's what I've found so far in my research:
This SO question is the closest I've found. The second answer there says
You can only convert List<T1> to ICollection<T2> if the types T1 and T2 are the same.
So I'm converting from List<Facility> to ICollection<IAmAHierarchyNode>. My Facility class implements IAmAHierarchyNode, so I don't understand why they wouldn't be considered the same.
Sadly, this SO question is a typo on the OP's part. This one is in reference to explicit interface implementations and the answers there would lead me to think that I shouldn't be having a problem.
You can add the cast but it will not work at runtime.
In .NET different instantiation of generic classes or interfaces are treated as separate types at runtime, so two generic instantiations do not share an inheritance relationship, even if their generic parameters share an inheritance relation:
class A { }
class B: A { }
List<B> b = new List<B>();
ICollection<A> ca = (ICollection<A>)b; // Will cause a runtime error, you can cast at compile time but the cast will fail at runtime.
There is one exception to this rule covariant/contravariant interfaces. IEnumerable<out T> is covariant, so you can assign an enumerable of a derived type to a reference of an enumerable of a base type
List<B> b = new List<B>();
IEnumerable<A> ca = b; // This works without any cast
The problem with allowing the cast you want is that ICollection<T> allows you to add to the collection. In the example above if you did manage to cast an ICollection<B>to ICollection<A>, and you have another derived type class C : A{} you could pass C to the reference of ICollection<A> which actually expects Bs
class A { }
class B: A { }
class C: A { }
List<B> b = new List<B>();
ICollection<A> ca = (ICollection<A>)b; 
ca.Add(new C()); // This would be legal if the above cast would succeed,    
                 // and you would have a List<B> containing a C
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With