Roslyn: Generic interface inheritance conflict with extension method

Created on 31 Jan 2017  路  11Comments  路  Source: dotnet/roslyn

I write 2 class inherit each other and each implement IEnumerable with difference generic type

Like this

```C#
public class A : IEnumerable
{
public IEnumerator GetEnumerator()
{
throw new NotImplementedException();
}

IEnumerator IEnumerable.GetEnumerator()
{
    throw new NotImplementedException();
}

}

public class B : A, IEnumerable
{
public new IEnumerator GetEnumerator()
{
throw new NotImplementedException();
}
}

////

static void Test()
{
    var a = new A();
    var b = new B();

    a.Select((item,i) => i);
    b.Select((item,i) => i);
}

```

Instance from A work fine but B cannot use all generic function in System.Linq

image

It seem B was seen by compiler that it is just IEnumerable but not IEnumerable<string>. It still have Cast<> and OfType<> exposed

image

Area-Compilers Bug Concept-Diagnostic Clarity help wanted

Most helpful comment

@Thaina

When I foreach B it return string not object so extension method should be the same

That's not how foreach works. It doesn't care about any inheritance tree, it cares about whether the types fit the enumerable pattern. All the compiler cares about is whether there is an accessible GetEnumerator method. You don't even have to implement IEnumerable or IEnumerable<T>.

If you were to reverse your example and have GetEnumerator public on A and explicitly implemented on B then foreach would iterate over IEnumerator<object> instead. The compiler would implicitly insert casts from object to string so that detail might not be noticed unless the two enumerators returned different values.

However, if both A and B explicitly implement all of the members of IEnumerable<string> and IEnumerable<object> then trying to foreach over B would result in a compiler error since the compiler cannot determine which of the two interfaces it should enumerate.

Like using new keyword to override function with the same name. And to get previous implementation then we need to explicitly specified generic type

To the CLR, IEnumerable<object> and IEnumerable<string> are different types. There is no support for variance in interface implementation between a base class and its derived classes. Although that would be interesting.

All 11 comments

It's because B implements both IEnumerable<object> and IEnumerable<string> so it's not possible for the compiler to resolve which generic type arguments to use with the extension method. The error message isn't quite clear about that but I think that's the fallback message anytime extension methods fail to resolve.

If you tried to call the extension method directly with the generic type argument inferred you'd get the following error message:

/*
error CS0411: The type arguments for method
    'Enumerable.Select<TSource, TResult>(IEnumerable<TSource>, Func<TSource, TResult>)'
    cannot be inferred from the usage.
    Try specifying the type arguments explicitly.
*/
Enumerable.Select(b, (item, i) => i);

Calling with the generic type arguments explicitly specified does work:

Enumerable.Select<string, int>(b, (item, i) => i);

I think compiler should select the latest implementation as default. Just like foreach

When I foreach B it return string not object so extension method should be the same

I think when we implement same interface on parent and derived class. It explicit enough to state that later implementation would be first priority to resolve. Like using new keyword to override function with the same name. And to get previous implementation then we need to explicitly specified generic type

Or should we have something like new on interface declaration?

```C#
public class A : IEnumerable
{
public IEnumerator GetEnumerator()
{
throw new NotImplementedException();
}

IEnumerator IEnumerable.GetEnumerator()
{
    throw new NotImplementedException();
}

}

public class B : A,new IEnumerable // ??
{
public new IEnumerator GetEnumerator()
{
throw new NotImplementedException();
}
}
```

@Thaina

When I foreach B it return string not object so extension method should be the same

That's not how foreach works. It doesn't care about any inheritance tree, it cares about whether the types fit the enumerable pattern. All the compiler cares about is whether there is an accessible GetEnumerator method. You don't even have to implement IEnumerable or IEnumerable<T>.

If you were to reverse your example and have GetEnumerator public on A and explicitly implemented on B then foreach would iterate over IEnumerator<object> instead. The compiler would implicitly insert casts from object to string so that detail might not be noticed unless the two enumerators returned different values.

However, if both A and B explicitly implement all of the members of IEnumerable<string> and IEnumerable<object> then trying to foreach over B would result in a compiler error since the compiler cannot determine which of the two interfaces it should enumerate.

Like using new keyword to override function with the same name. And to get previous implementation then we need to explicitly specified generic type

To the CLR, IEnumerable<object> and IEnumerable<string> are different types. There is no support for variance in interface implementation between a base class and its derived classes. Although that would be interesting.

I know foreach does not look at interface. I just mean what I get from foreach should be the same thing I got from Linq

So, I think generic extension method resolver should just look at latest type it was implemented in the chain first. What could be problem on this assumption?

Why not solve this by making A generic as well?

//maybe make this class abstract if it contains shared logic
//for other derived types and have a separate class
//inheriting A<object> if you need it
public class A<T> : IEnumerable<T>
{
    //....
}

public class B : A<string>
{
    //....
}

@Joe4evr Because actually what I extend from is newtonsoft JArray and JObject which I don't wrote it myself

Currently I just write my own extension method For<T>(this JArray) to just convert it. Which is not clean in my opinion

@Thina Why do you use your own implementation instead of Cast<T>?

@eyalsk Because I'm not sure what will happen when we call JArray.Cast<int>() or JObject.Cast<MyOwnClass>(). I don't know how JArray implement

Also I was create my own IEnumerable<KeyValuePair<string,T>> JObject.For<T>() because JObject itself return JToken

@Thaina JObject, JArray and JToken implement Value<T> and Values<T>, can't you use them? I'm not sure what you're doing exactly or trying to do but conversion between types seems like a very basic feature that such a library should have and it does. :)

@eyalsk In short. Please believe me that I have my own specific logic that really need to implement it like this and all those easy workaround does not work. Please believe me that I already try most of those easy option

To clarify it. I try to make a class extended from JArray and JObject as a wrapper for intellisense. All of the property in that class only get/set

```C#
public class MyClass : JObject
{
public MyClass(JObject jobj) : base(jobj) { }
public int number { get { return this.Get("value",0); } set { return this["value"] = value; } }
public int? value { get { return this.TryGet("value"); } set { return this["value"] = value; } }
}


With this way of code I will get intellisense for property I know while still maintain all original json data that may contain extra property

But I implement my own `Get` function because whenever the thing I call `Get` was derived from JArray or JObject, it will replace that property. Which `Value<>` and `Values<>` don't do it for me

```C#
public class MyParentClass : JObject
{
    public MyParentClass(JObject jobj) : base(jobj) { }
    public MyClass child { get { return this.Get<MyClass>("child"); } set { return this["child"] = value; } }
}

public static class ExtJson
{
    public T Get<T>(this JObject jobj,string key)
    {
        var obj = jobj[key].ToObject<T>();
        if(obj is JObject)
            jobj[key] = obj as JObject;
        return obj;
    }
}

And so I wrap all json system with me own Get

but the main issue came when I just want to make MyClass being Dictionary. that's why I need to implement my own For to wrap everything around it

Well this is frustrate me but I already request feature intellisense on dynamic but you just mess with that issue. And so for now I need to workaround like this. But even have so much hard time with it it still not a neat way

I will not need anything like this if I could just make dynamic intellisense

@Thaina

Well this is frustrate me but I already request feature intellisense on dynamic but you just mess with that issue. And so for now I need to workaround like this. But even have so much hard time with it it still not a neat way

I didn't try to mess with you or make a mess, I just wrote my opinion and regardless to what I wrote the issue #15974 is actually marked with Feature Request and Backlog.

I make mistakes, I write things that sometimes don't make sense and people here help or correct me, you shouldn't feel frustrated, you should have fun and appreciate it. :)

I will not need anything like this if I could just make dynamic intellisense

And it might happen. :)

Was this page helpful?
0 / 5 - 0 ratings