简体   繁体   中英

Why can't I cast EntityCollection<TEntity> to ICollection<Object>?

If I try to cast an object of type EntityCollection<MyNamespace.Models.MyEntityClass> to ICollection<Object> I get an InvalidCastException .

Okay, according to the docs, EntityCollection<TEntity> implements ICollection<T> , and MyNamespace.Models.MyEntityClass must descend from Object , right? So why on earth does this fail?

FWIW, I'm trying to do this in a method that generally can add and remove items from what might be an EntityCollection or some other IList or ISet . I need to preserve the change tracking behavior of EntityCollection, because the object is to eventually be able to commit the changes if it's an EC.

Edit

Okay, here's some more specifics of what I'm doing. I'm deserializing JSON, and the target object can have properties that are collections--maybe they're EntityCollection s, maybe not. For the sake of simplicity, lets say the members of the collection are always subclasses of EntityObject , whether it's an EntityCollection or not (if I understand the responses so far, I'd have no better luck casting to ICollection<EntityObject> than to ICollection<Object> …right?). This is the part where I run into trouble…

foreach (PropertyInfo prop in hasManys)
{
    // This is where I get the InvalidCastException...
    ICollection<Object> oldHms = (ICollection<Object>)prop.GetValue(parentObj, null);

    JEnumerable<JToken> hmIds = links[FormatPropName(prop.Name)].Children();
    if (hmIds.Count() == 0)
    {
        // No members! Clear it out!
        oldHms.Clear();
        continue; // breaking early!
    }

    relType = prop.PropertyType.GetGenericArguments()[0];

    // Get back the actual entities we'll need to put into the relationship...
    List<EntityObject> newHms = new List<EntityObject>();
    foreach (JToken jt in hmIds)
    {
        // ...populate newHms with existing EntityObjects from the context...
    }

        // first, delete any missing...
        /* Got to use ToList() to make a copy, because otherwise missings is 
         * still connected to the oldHms collection (It's an ObjectQuery)
         * and you can't modify oldHms while enumerating missings. 
         */
        // This cast will fail too, right? Though it's more easily fixable:
        IEnumerable<EntityObject> missings = ((ICollection<EntityObject>)oldHms).Except(newHms).ToList();
        foreach (EntityObject missing in missings)
        {
            oldHms.Remove(missing); // One of my mutable collection operations
        }

        // add new ones
        foreach (EntityObject child in newHms)
        {
            if (!oldHms.Contains(child)) // Skip if already in there
            {
                oldHms.Add(child); // another mutable collection operation
            }
        }

    }

}

That's a bit simplified, I have special cases for Arrays (implement ICollection, but aren't generics) and other stuff that I took out. Point is, I need to operate Clear , Add , and Remove on the EntityCollection itself--if that's what it is. Maybe there's another way to do this type of synchronization that I'm missing?

Since ICollection<T> hasn't variance, ICollection<MyEntityClass> and ICollection<object> are different types, unrelated to each other.

I'm trying to do this in a method that generally can add and remove items from what might be an EntityCollection or some other IList or ISet

So, why don't you work with IList ? Looks like you don't care about real type of items in this method.

read-write collections cannot be variant.

Take this example:

List<MyClass> list1 = new List<MyClass>();

// assume this would work
ICollection<object> list2 = list1;

list2.Add(new object()); // ooops. We added an object to List<MyClass>!

In principal this kind of casting is only possible for "read-only" interfaces (allowing covariance) or for "write-only" interfaces (allowing contravariance).

One "solution" would involve a wrapper class like this:

public class Wrapper<T> : ICollection<object>
{
    private readonly ICollection<T> collection;
    public Wrapper(ICollection<T> collection)
    {
        this.collection = collection;
    }


    public void Add(object item)
    {
        // maybe check if T is of the desired type
        collection.Add((T)item);
    }

    public void Clear()
    {
        collection.Clear();
    }

    public bool Contains(object item)
    {
        // maybe check if T is of the desired type
        return collection.Contains((T)item);
    }

    public void CopyTo(object[] array, int arrayIndex)
    {
        // maybe check if T is of the desired type
        collection.CopyTo(array.Cast<T>().ToArray(), arrayIndex);
    }

    public int Count
    {
        get { return collection.Count; }
    }

    public bool IsReadOnly
    {
        get { return collection.IsReadOnly; }
    }

    public bool Remove(object item)
    {
        // maybe check if T is of the desired type
        return collection.Remove((T)item);
    }

    public IEnumerator<object> GetEnumerator()
    {
        yield return collection;
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return collection.GetEnumerator();
    }
}

Instead of

 EntityCollection<MyNamespace.Models.MyEntityClass> collection = ...;
 ICollection<Object> generic = collection ;

you would have to write:

 EntityCollection<MyNamespace.Models.MyEntityClass> collection = ...;
 ICollection<Object> generic = new Wrapper(collection);

And could adjust the wrapper class at the points marked by comments how to deal with type problems.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM