2

We have an ObservableCollection<T> of 6 ObservableCollection List<Parent> which all have different types of Child classes.

What we would like to do is use a generic method to retrieve all objects which have same type, so in other words retrieve a list which contains all <T> children.

Here is my source code

classes A and B are child classes of Parent.

ObservableCollection<ManagerTemplate> ManagerListStack = new ObservableCollection<ManagerTemplate>(ManagerTemplates);


class ManagerTemplate
{
 public Type _Type { get; set; }
 public ObservableCollection<Parents> parentList {get;set;}
}

internal static List<ManagerTemplate> ManagerTemplates = new List<ManagerTemplate>()
{
    new ManagerTemplate{ List= new ObservableCollection<Parent>(),Type=typeof(A)},
    new ManagerTemplate{ List= new ObservableCollection<Parent>(),Type=typeof(B)}
};

public static List<T> Get<T>() where T : Parent
{
    /*(ManagerListStack.Where(x => x._Type == typeof(T)).First().List.Cast<T>()).ToList(); -- TRY 1*/
    /*(List<T>)(ManagerListStack.Where(x => x._Type == typeof(T)).First().List.Cast<T>())* -- TRY 2*/
    return  (ManagerListStack.Where(x => x._Type == typeof(T)).First().List.Cast<T>()).ToList();
}

Using

(ManagerListStack.Where(x => x._Type == typeof(T)).First().List  as List<T>)

The returned list contains no elements.I am 100% sure and have debugged the list, and there are elements inside.

Using

(List<T>)(ManagerListStack.Where(x => x._Type == typeof(T)).First().List.Cast<T>())

I get an error 'Unable to cast from Parent to A or B'

user2920222
  • 197
  • 1
  • 2
  • 8
  • do I understand it correctly that ManagerTemplate will always hold a single ObeservableCollection of a single type of Parent? – Martijn Apr 06 '14 at 18:14
  • Yes, correct. Inside each ManagerTemplate will be a single ObserableCollection which contains objects of same type. – user2920222 Apr 06 '14 at 18:18
  • 1
    And what is your question? You seem to have a solution already. – Olivier Jacot-Descombes Apr 06 '14 at 18:22
  • The solution doesn't work. – user2920222 Apr 06 '14 at 18:26
  • 1
    `SelectMany` to flatten and than `OfType` to select may produce easier to read code... Note that "The solution doesn't work" is bad explanation of problem - you should debug it first and see what exactly does not work - trying to get complicated filtering in one statement is not the easiest way to investigate problems with LINQ... – Alexei Levenkov Apr 06 '14 at 18:29
  • What happens? Compiler error (which one), runtime exception (which one), not desired result? – Olivier Jacot-Descombes Apr 06 '14 at 18:29
  • Downvoted because I can't get the OP's code to compile for multiple reasons, including there not being a `List` property on `ManagerTemplate`. – Edmund Schweppe Apr 06 '14 at 23:29

2 Answers2

1

SomeListOfX as List<Y> will never work. The fact that Y derives from X does not mean that List<Y> derives from List<X>! These two list types are not compatible; they are simply two different types.

A List<Parent> cannot be cast to a List<Child>, even if it contains only items of type Child because C# knows only the static types when it compiles, not the runtime types. The list could contain items that are not of type Child.

By the way, the opposite doesn't work either. Because if you were able to cast a List<Child> to a List<Parent>, then you could add an item of type Parent or AnotherChild to the List<Parent>, but since the underlying list is still of type List<Child> this would f*** up! Note, casting an object does not create a new object (i.e. it does not transform the object), it just tells C# to consider it as being another type. E.g. you can say Child child = (Child)parent; if you know that parent references a child.


In

(List<T>)(ManagerListStack.Where(x => x._Type == typeof(T)).First().List.Cast<T>())

Cast<T> yields an IEnumerable<T> and you cannot cast an IEnumerable<T> to a List<T>! An enumerable is not a list.


What works is

List<Y> listOfY = listOfX.Cast<Y>().ToList();

if X can be cast to Y.


Your third (uncommented) example in Get<T> works:

return ManagerListStack
    .Where(x => x._Type == typeof(T))
    .First().List
    .Cast<T>()
    .ToList();
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
1

First of, like Olivier said above, a List<Child> is not a subclass of List<Parent>, even if Child is a child of Parent. The same goes for ObservableCollection. This subject is called variance. It goes too far to discuss it deeply here, but you could take a look at C# : Is Variance (Covariance / Contravariance) another word for Polymorphism? for more details. IEnumerable<T> is covariant. I'm going to switch to that. If you specifically need list or observable collections, you can trivially construct them afterwards, or even feed them in.

What you would like to have is a strongly typed Get function, that returns an arbitrary IEnumerable from an ObservableCollection> where U is a child of T. Lets see if we can write that signature:

IEnumerable<U> Get<U, T>(ObservableCollection<IEnumerable<T>> source) where U : T

that's the most abstract version of what we want, but we don't actually need it to be that generic: we already know the base type at compile time. For easier reading, we can make it more specific:

IEnumerable<U> Get<U>(ObservableCollection<IEnumerable<Parent>> source) where U : Parent

Now lets fill in the body of that function:

IEnumerable<U> Get<U>(ObservableCollection<IEnumerable<Parent>> source) where U : Parent {
  return source.First(inner => inner is IEnumerable<U>)
}

Wow, that was actually quite simple!

This is not really the api you want. If we put everything together, we get

ObservableCollection<IEnumerable<Parent>> ManagerListStack = new ObservableCollection<IEnumerable<Parent>>(ManagerTemplates);


internal static List<IEnumerable<Parent>> ManagerTemplates = new List<IEnumerable<Parent>>
{
    new IEnumerable<ChildA>(){},
    new IEnumerable<ChildB>(){},
};

IEnumerable<U> Get<U>() where U : Parent {
  return ManagerListStack.First(inner => inner is IEnumerable<U>)
}

now, we still don't have the behaviour you want: ObservableCollections of ObservableCollections. It turns out that we can't properly do this in C#'s type system. What we can do however, is shift the responsibility from the type system to the programmer. We keep the ObservableCollection<IEnumerable<Parent>>, but we fill it with ObservableCollections<Child>. This is possible because ObservableCollection<Child> imlements IEnumerable<Child>, and IEnumerable<Child> is a subtype of IEnumerable<Parent>.

ObservableCollection<IEnumerable<Parent>> ManagerListStack = new ObservableCollection<IEnumerable<Parent>>(ManagerTemplates);


internal static List<IEnumerable<Parent>> ManagerTemplates = new List<IEnumerable<Parent>>
{
    new ObservableCollection<ChildA>(){},
    new ObservableCollection<ChildB>(){},
};

ObservableCollection<U> Get<U>() where U : Parent {
  return (ObservableCollection<U>)ManagerListStack.First(inner => inner is ObservableCollection<U>)
}

we use the cast as an escape hatch to get around the limitations of the library that doesn't provide a co-variant ObservableCollection (which seems impossible anyway, so don't blame them for it)

The full program I used in LinqPad to test:

void Main()
{
  new Test().Get<ChildA>().First().Talk();
}

class Test {

        internal List<IEnumerable<Parent>> ManagerTemplates;
        private ObservableCollection<IEnumerable<Parent>> ManagerListStack;

        public Test() {
        this.ManagerTemplates = new List<IEnumerable<Parent>>
        {
            new ObservableCollection<ChildA>(){ new ChildA()},
            new ObservableCollection<ChildB>(){ new ChildB()},
        };

         this.ManagerListStack = new ObservableCollection<IEnumerable<Parent>>(this.ManagerTemplates);
         }

        public ObservableCollection<U> Get<U>() where U : Parent {
          return (ObservableCollection<U>)ManagerListStack.FirstOrDefault(inner => inner is ObservableCollection<U>);
        }
}

abstract class Parent {
    abstract public void Talk();
}

class ChildA : Parent {
    public override void Talk(){
      Console.WriteLine("I'm an A");
    }

}

class ChildB : Parent {
public override void Talk(){
      Console.WriteLine("I'm a B");
    }
}

As expected, it prints "I'm an A"

Community
  • 1
  • 1
Martijn
  • 11,964
  • 12
  • 50
  • 96