13
votes

Coming back to C# after a few years so I'm a little rusty. Came across this (simplified) code and it's leaving my head scratching.

Why do you have to explicitly implement the IDataItem.Children property? Doesn't the normal Children property satisfy the requirement? After all, the property is used directly to satisfy it. Why is it not implicit?

public interface IDataItem {

    IEnumerable<string> Children { get; }
}

public class DataItem : IDataItem {

    public Collection<string> Children { get; } = new Collection<string>();

    // Why doesn't 'Children' above implement this automatically?!
    // After all it's used directly to satisfy the requirement!
    IEnumerable<string> IDataItem.Children => Children;
}

According to the C# source, here's the definition of Collection<T>:

[System.Runtime.InteropServices.ComVisible(false)]
public class Collection<T> :
    System.Collections.Generic.ICollection<T>,
    System.Collections.Generic.IEnumerable<T>, <-- Right Here
    System.Collections.Generic.IList<T>,
    System.Collections.Generic.IReadOnlyCollection<T>,
    System.Collections.Generic.IReadOnlyList<T>,
    System.Collections.IList

As you can see, it explicitly implements IEnumerable<T> and from my understanding, if 'X' implements 'Y' then 'X' is a 'Y', so Collection<String> is an IEnumerable<String> so I'm not sure why it isn't satisfied.

4
It's not used "directly", there's an implicit conversion (upcast) which the compiler will turn into a non-zero amount of code.Ben Voigt
Doesn't the normal Children property satisfy the requirement? - No. Based on my understanding, the implementation must exactly match the interface signature including the return type.Thangadurai
C# does no support return type covariance.user4003407
You don't have to use IDataItem.Children explicitly, public IEnumerable<string> Children => new Collection<string>(); is good enough.Guy
@Guy: But then he has no way to use the Collection object as a Collection from inside the class :(Ben Voigt

4 Answers

7
votes

Perhaps this example makes it clearer. We want signatures to match exactly1,2, no substitutions allowed, despite any inheritance relationships between the types.

We're not allowed to write this:

public interface IDataItem {

    void DoStuff(string value);
}

public class DataItem : IDataItem {

    public void DoStuff(object value) { }
}

Your example is the same, except your asking for return types rather than parameters (and employing a narrowing rather than widening conversion, for obvious reasons). Nontheless, the same principal applies. When it comes to matching signatures, the types must match exactly3.

You can ask for a language that would allow such things to happen and such languages may exist. But the fact of the matter is, these are the rules of C#.


1Outside of some limited support for Co- and Contra-variance involving generics and interfaces/delgates.

2Some may argue about whether signature is the right word to use here since in this case, return types matter as much as parameter types, generic arity, etc; In most other situations where someone talks about C# method signatures, they'll be explicitly ignoring the return type because they're (explicitly or implicitly) considering what the overloading rules say, and for overloading, return types are not part of the signature.

Nonetheless, I'm happy with my usage of the word "signature" here. Signatures aren't formally defined in the C# specification and where it's used, it's often to point out which parts of the signature aren't to be considered for overloading.

3Not to mention the issues that would be raised if your Children method was returning a struct that happened to implement IEnumerable<string>. Now you've got a method that returns the value of a value type and a caller (via the IDataItem interface who is expecting to receive a reference to an object.

So it's not even that the method could be used as-is. We'd have to (in this case) have hidden boxing conversions to implement the interface. When this part of C# was specced, they were, I believe, trying not to have too much "hidden magic" for code you could easily write yourself.

5
votes

In your example your "normal" Children property do not actually satisfy the interface requirement. The type is different. It does not really matter that you can cast it - they are different.

Similar example and maybe a bit more obvious is if you would implement an interface with a actual method that returns IEnumerable and tried an ICollection method from the actual class. There is still that compile time error.

As @Ben Voigt said the conversion still generates some code, and if you want to have it - you need to add it implicitly.

0
votes

The question has been answered here (you have to match the interface types) and we can demonstrate the problem with an example. If this worked:

public interface IDataItem {

    IEnumerable<string> Children { get; set; }

    void Wipe();
}

public class DataItem : IDataItem {

    public Collection<string> Children { get; } = new Collection<string>(); 

    public void Wipe() {
        Children.ClearItems();  //Property exists on Collection, but not on IEnumerable
    }
}

And we then use this code like this:

IDataItem x = new DataItem();

//Should be legal, as Queue implements IEnumerable. Note entirely sure
//how this would work, but this is the system you are asking about.
x.Children = new Queue<string>();

x.Wipe();  // Will fail, as Queue does not have a ClearItems method within

Either you mean the property to only ever be enumerable, or you need the properties of the Collection class - type your interface appropriately.

-1
votes

Any class that implements the interface must contain the definition for method that matches the signature that the interface specifies. The interface defines only the signature. In that way, an interface in C# is similar to an abstract class in which all the methods are abstract.

Interfaces can contain methods, properties, events, indexers, or any combination of those four member types.

Here is the good read about interfaces. Interfaces in C#

Hope it will help you in further understanding.