简体   繁体   中英

Why does this extension method work with generics but not a set base type?

In my project I have the following class structure:

public interface IUpdateableModel
{
    ModelState State { get; set; }
    void ResetState();
}

public abstract class UpdateableModel : IUpdateableModel
{
    public ModelState State { get; set; }
    public void ResetState()
    {
        //Perform reset logic
    }
}

public class MyUpdateableClass : UpdateableModel
{
    //Some properties.
}

Now I am trying to add some extension methods for use with collections of IUpdateable :

public static class UpdateableModelExtensions
{
    public static bool HasUnsavedChanges(this IList<IUpdateableModel> collection)
    {
        return collection.Any(x => x.State != ModelState.Unmodified);
    }

     public static void ResetItemStates<T>(this IList<T> collection) where T : IUpdateableModel
    {
        var itemsToRemove = collection.Where(x => x.State == ModelState.New).ToList();
        foreach (var item in itemsToRemove)
        {
            collection.Remove(item);
        }

        var itemsToAdd = collection.Where(x => x.State == ModelState.Deleted).ToList();
        foreach (var item in itemsToAdd)
        {
            item.State = ModelState.Unmodified;
        }

        var itemsToReset = collection.Where(x => x.State == ModelState.Modified).ToList();
        foreach (var item in itemsToReset)
        {
            item.ResetState();
        }
    }
}

As written when using this on a List<MyUpdateableClass> a compiler error is produced that the types do no match up.

public class MyClass
{
    public IList<MyUpdateableClass> Items {get; set;}

    public void MyMethod()
    {
         if(Items.HasUnsavedChanges()) //Compiler error
         {
            //Do some stuff
         }
    }
}   

The compiler error is:

 'IList<MyUpdateableModel>' does not contain a definition for
 'HasUnsavedChanges' and the best extension method overload
 'UpdateableModelExtensions.HasUnsavedChanges(IList<IUpdateableModel>)'
 requires a receiver of type 'IList<IUpdateableModel>'

The same result is seen if the extension method is changed to IList<UpdateableModel>

However if I instead use generics to implement this, it works fine:

public static bool HasUnsavedChanged<T>(this IList<T> collection) 
where T : IUpdateableModel
    {
        return collection.Any(x => x.State != ModelState.Unmodified);
    }

Also If I change the usage to Items.Cast<IUpdateableModel>().ToList() the first version does work.

So what are the technical details that allow the generic version to work when the concrete version does not?

This is because the IList contents are more specific than the signature allows. This could result in a violation of the implied contract.

The contract of IList<IUpdateableModel> is that any implementer of IUpdateableModel must be able to be added to the list. This is not possible for a List<ImplementationOfUpdateableModel> since you could only add objects of type ImplementationOfUpdateableModel .

The generic version works because it allows the method to accept ILists of more specific object contents.

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