Pure.Krome
Pure.Krome

Reputation: 87047

How to implement this generic interface for this C# method

I have the following interface:

public interface ICopyable
{
    void Copy<T>(T source) where T : class;
}

I've tried to implement it, but I keep getting this error:

The constraints for type parameter 'TListing' of method 'Foo.Core.Models.AggregateRoot.Copy(TListing)' must match the constraints for type parameter 'T' of interface method 'FooCore.Interfaces.ICopyable.Copy(T)'. Consider using an explicit interface implementation instead.

public abstract class AggregateRoot : ICopyable
{
    public virtual void Copy<TListing>(TListing newAggregateRoot) 
        where TListing : AggregateRoot
    {
       // my code here.
    }
}

enter image description here

Why does it fail? My AggregateRoot is a class.

Upvotes: 0

Views: 980

Answers (1)

Theodoros Chatzigiannakis
Theodoros Chatzigiannakis

Reputation: 29233

Let's say your code compiled. You could then do this:

class Derived : AggregateRoot
{
}

And then:

var derived = new Derived();
ICopyable copyable = derived;
copyable.Copy<string>("Hello!");

This would be fine, because ICopyable.Copy<T> requires a type argument that's a class - and string is a class. But it would have to be dispatched to a method that actually expects a T : AggregateRoot. This would have to fail at runtime - so the compiler simply rejects it beforehand.

In more general terms, the problem is that you are trying to implement a specific interface with an implementation that is more strict than what the interface says. This is not type safe - and it would violate the Liskov Substitution Principle with respect to contravariance of inputs.

But even if it was type safe and it didn't violate the LSP, it would still be rejected by the compiler. C# requires that specializing a class and implementing an interface is always done with exact, invariant signatures and identical constraints. This is spelled out in the C# specification, in 13.4.3 Implementation of generic methods:

When a generic method implicitly implements an interface method, the constraints given for each method type parameter must be equivalent in both declarations [...]

But still, C# allows variance in implicit conversions between generic interfaces (of the same generic type definition). Thus, your code would work if it was changed to something like this:

public interface ICopyable<in T>
    where T : class
{
    void Copy(T source);
}

public abstract class AggregateRoot : ICopyable<AggregateRoot>
{
    public virtual void Copy(AggregateRoot newAggregateRoot) 
    {
       // my code here.
    }
}

And now you can do the following, which compiles and is type safe:

var derived = new Derived();
ICopyable<AggregateRoot> aggregateCopyable = derived;
ICopyable<Derived> derivedCopyable = aggregateCopyable;
derivedCopyable.Copy(derived);

Upvotes: 6

Related Questions