dotnet / csharplang

The official repo for the design of the C# programming language
11.5k stars 1.03k forks source link

Hide interface members of derived interface (on interface level) #1163

Closed Rand-Random closed 6 years ago

Rand-Random commented 6 years ago

For example:

public interface IList
{
    void Add(object item);
}

public interface IList<T> : IList
{
    void Add(T item);
    void IList.Add(object item); //gets hidden from the interface, used the explicit interface implementation syntax
}

public class List<T> : IList<T>
{
    public void Add(T item)
    {

    }

    void IFoo.Add(object item)
    {
        this.Add((T)item);
    }
}

private void Foo()
{
    var list =new List<T>();
    list.Add(new T()); //only the void Add(T item) is accessable, since the void Add(object item) is explicit implemented

    ((IList<T>)list).Add(new T()); //only the void Add(T item) should be accessable, since void Add(object item) is hidden on interface level

    ((IList)list).Add(new T()); //only the void Add(object item) is accessable
}
MkazemAkhgary commented 6 years ago

there is no need to hide interface member. you are basically rewriting exact same method. you are good with

public interface IList<T> : IList
{
    void Add(T item);
}

and you can implement interface explicitly in this way

public class List<T> : IList<T>
{
    public void Add(T item) ...

    void IList.Add(object item) => this.Add((T)item);
}
Rand-Random commented 6 years ago

@MkazemAkhgary

You are missing the point, I am proposing a way to hide it on interface level you only hide it on the class. Additionally, you are doing the exact same thing as I did in my original post.

Without my proposal this will compile:

IList<int> list = new List<int>();
list.Add("Hello World"); //this will compile since the interface IList<T> derives from IList and it will use the void (object item);
MkazemAkhgary commented 6 years ago

are you sure that IList<T> implements IList? http://referencesource.microsoft.com/#mscorlib/system/collections/generic/ilist.cs,b19f71a84062554b

Rand-Random commented 6 years ago

@MkazemAkhgary

I am sure that it doesn't implement it.

I just used IList<T> and IList, since they are known interfaces and its a known limitation that IList<T> doesnt inherit IList because of the accessability of void Add(object item) (and other methods, that take T as paramter), if it would implement it.

Thats one of the reason why I am proposing a way to hide an interface member on interface level.

So IList<T> could inherit from IList, ICollection<T> could inherit from ICollection and so on. The only reason, I am aware of, is the lack of hiding the members, hence my proposal.

But, I could also change my propasal to IFoo<T> and IFoo if its confusing to use an existing interface.

As you can see here that this problem is known: https://stackoverflow.com/questions/14559070/why-generic-ilist-does-not-inherit-non-generic-ilist https://blogs.msdn.microsoft.com/brada/2005/01/18/why-does-ienumerable-inherits-from-ienumerable/

Rand-Random commented 6 years ago

@VisualMelon

As in my original post the implementation is the same as withouth the hidden members. You arent hiding it from the declaration point of view, just from its accessability in the interface level.

One could also say it like this:

public interface IList<T> : IList
{
    void Add(T item);
    private void IList.Add(object item); //declare the method "private" on the interface level, to restrict the access when you have a variable of IList<T> but that doesnt influence IList in anyway
}

You would still need to implement Add(object item) of IList, you can also decide to hide it from the class by explicit implementing the method, as I did in the class above.

What would it solve is simple:

IList<int> list = new List<T>();
DoStuff(list); //currently you can't pass IList<int> to the method DoStuff since IList<int> doesn't implement IList, with my proposal this would work

private void DoStuff(IList list)
{

}

To fix this you could already do interface IList<T> : IList and you could pass it to DoStuff(IList list) but this intruduces the problems that all non-generic methods of IList are accessable next to its generic counter parts, so this will compile: (again talking about the interface level, I know you can use explicit implementation to hide the non-generic methods on class level)

IList<int> list = new List<T>();
DoStuff(list);

//both methods are accessable if you simple inherit from IList, and it will compile just fine
list.Add("Hello World");
list.Add(1);

private void DoStuff(IList list)
{

}

With my proposal both could be solved, the missing inheritance of IList and the undesired accessability if you would inherit from IList.

VisualMelon commented 6 years ago

@Rand-Random yes, sorry, I misunderstood first time (hence prior deletion of confused post): you might want to add a bit more explanation to the OP, I think MkazemAkhgary and I have both managed to miss the point!

However, I'm not clear on what utility this proposal is meant to provide. If an interface is incompatible with another interface there is no way that it should be 'implementing' the other. Attempting to hide 'problem' methods isn't a solution, because they are still safely accessible, and still require implementation (indeed, your example shows implicit projection of IList<T> to IList, which doesn't require any acknowledgement from the caller).

On the point of IList<T> and IList, an IList<T> is not an IList quite simply because they have different interfaces. An IList is a different interface to an IList<T>, they are not compatible. If you want to pass an IList<T> as an IList then you have a conceptual problem that cannot be solved by changing the interface for IList<T> to be an extension of IList: that would change the meaning of IList<T>. Really, you are talking about a new interface which inherit both IList and IList<T>.

Further, not only does the example break IList<T> (because now it is expected to handle things that aren't T), it also violates IList, which is expected to cope with any object input, not crash if you pass it something that it secretly wasn't expecting. An IList<T> shouldn't implement IList, and any attempt to implicitly create an IList<T> as an IList will create misery. If you find yourself passing an IList<T> to an IList, then you are violating the contract set out by the method you are calling (which expects and IList).

I would argue that there is no problem here, there is only a situation which poses an inherent conceptual concern, and that concern must be addressed on a case-by-case basis by the designer/programmer/consumer: no change to the library can make it go away.

I haven't read into the relevant proposals recently, but I think extension interface implementation may allow you achieve what you want (i.e. provide an implementation of IList for all IList<T>) which bakes in the decision that it should just attempt to cast to a T. This would effectively allow a blanket decision about how to deal with such situations - which I would consider a very bad idea - without a breaking change.

Going back to the proposal, I can't really see any benefit in having interfaces not expose some methods of extended interfaces; it seems only to confuse the calling of such methods by requiring an explicit cast (which is otherwise not expected in C#).

HaloFour commented 6 years ago

There's nothing about the CLR or the C# language which prevent an interface like IList<T> from implementing IList. The add method does collide and requires a consumer to explicitly implement one or the other, but that's no different than the case with IEnumerable and IEnumerable<T>. The reason that IList<T> doesn't extend IList is due to design concerns, not technical limitations. At this point it's way too late, changing IList<T> to implement IList would be a breaking change to all implementors of IList<T>.

@VisualMelon

Going back to the proposal, I can't really see any benefit in having interfaces not expose some methods of extended interfaces; it seems only to confuse the calling of such methods by requiring an explicit cast (which is otherwise not expected in C#).

Actually it's not uncommon. The CLR is pretty flexible with how a class may implement an interface. The implementing members only need to match the signature and be virtual. But they can have different names or accessibility. That's how C# explicit implementation works, the implementing method is private and has a different name, e.g. IList.Add, which is perfectly legal from an IL point of view but not a legal identifier in C#. VB.NET takes that further:

Public Class Foo
    Implements IDisposable

    Private Sub ShutUp() Implements IDisposable.Dispose
    End Sub    
End Class

With default interface methods, the class actually will not expose a public member of the default method unless it defines its own:

public interface IFoo
{
    void DoStuff() => Console.WriteLine("Doing stuff!");
}

public class Foo : IFoo { }

Foo foo = new Foo();
foo.DoStuff(); // compiler error
IFoo ifoo = foo;
ifoo.DoStuff(); // just fine
VisualMelon commented 6 years ago

@HaloFour indeed, there is surely no technical reason why this couldn't be done, but I would argue that calling a method on a class is very different to calling a method on an interface (I didn't wish to imply this was not the case with my comment about this being otherwise not expected in C#, but I can see how unclear that is now!).

However, I take the point... even if it seems a bit very odd now, I can't think of a real reason why interfaces need expose all of the inherited members directly. I don't much like the idea (in my head an interface in C# is an interface, not something that exposes more interfaces (which could be done in any number of ways)), but it does provide some new capability.

Can anyone think of any fun situations where it would be particularly useful that could help sell this?

Rand-Random commented 6 years ago

@HaloFour

As I previously stated, I only took IList<T> and IList since they are known and I thought it would be easier to understand instead of IFoo<T> and IFoo, as it turns out I was wrong.

Just let me clear it up:

I am not proposing that IList<T> implements IList, though I personally wouldnt mind if it did. I am only proposing to "hide" members of interfaces inside interfaces.

HaloFour commented 6 years ago

@Rand-Random

This would only make sense in the context of default interface methods, and that does seem like it will be supported:

public interface IFoo {
    void Add(object item);
}

public interface IFoo<T> : IFoo {
    void Add(T item);
    void IFoo.Add(object item) { this.Add((T)item); }
}

public class Foo<T> : IFoo<T> {
    public void Add(T item) => Console.WriteLine($"Added {item}.");
}

public class C {
    public void M() {
        Foo<int> foo = new Foo<int>();
        foo.Add(123);
        IFoo ifoo = foo;
        object boxed = 456;
        ifoo.Add(boxed);
    }
}
VisualMelon commented 6 years ago

A situation where this could be of value would be when an interface has to extent two interfaces which expose a method with the same signature:

interface A
{
    void DoStuff();
}
interface B
{
    void DoStuff();
}
interface C : A, B
{
}

You can't call DoStuff() on a C because it is ambiguous. This proposal could allow an indication as to which should be exposed (by hiding competing declarations), though I don't think it would an especially clear way of doing so. This seems like an edge case rather than a sensible use-case, but is perhaps worth keeping in mind.

HaloFour commented 6 years ago

@VisualMelon

You can already solve that by having C also declare DoStuff. That puts the onus of forwarding the call on the implementer, but with DIM that can be done by the interface:

interface A
{
    void DoStuff();
}

interface B
{
    void DoStuff();
}

interface C : A, B
{
    new void DoStuff() { base(B).DoStuff(); }
}

public class D : C
{
    public void DoStuff() { }
}

static class Program
{
    static void Main()
    {
        C c = new D();
        c.DoStuff();
    }
}
VisualMelon commented 6 years ago

@HaloFour granted, but handing off the decision to the implementation is very different from declaring it in the interface. Personally I'm not so fond of DIM, so it doesn't tend to occur to me (but much more clearly expresses the intent here). Again, though, it still allows the implementation to make the final choice, which is not good

HaloFour commented 6 years ago

@VisualMelon

Declaring it in the interface doesn't make sense without DIM. There's gotta be an implementation somewhere. Interfaces that extend other interfaces are just composite contracts otherwise, and A.DoStuff and B.DoStuff are two completely different contracts both requiring implementation.

VisualMelon commented 6 years ago

@HaloFour It does make sense without DIM: it defines a different (tighter) contract, and conveys more to the caller (I'm calling through to B here, not some other method in C). There is no C.DoStuff() to provide an implementation for, it just happens that C exposes B.DoStuff() and not A.DoStuff() (both of which must of course be implemented by anything that implements C). With DIM, if the Default Implementation is overridable then it is possible for the class implementing C to break the 'contract' that calling against C really calls against B and not A.

Whether 'default overloads' like these would ever be desirable or not is a different question (probably not!), but it certainly makes sense without requiring DIM.

HaloFour commented 6 years ago

@VisualMelon

Which violates the idea that it's a contract. A.DoStuff, B.DoStuff and C.DoStuff are all disparate contracts. The idea of "removing" a member doesn't make much sense, nor does it reduce the burden on the implementer who is still absolutely required to provide an implementation. Having DIM allow one of the contracts to shadow the implementation of the other reduces that burden and achieves the effect quite nicely.

DavidArno commented 6 years ago

@HaloFour,

At this point it's way too late, changing IList<T> to implement IList would be a breaking change to all implementors of IList<T>.

Surely that's exactly what the default interface methods feature is for? 😇

HaloFour commented 6 years ago

@DavidArno

Sure, DIM can be used to solve that evolution to the extent that there are reasonable default implementations for all of those members.

Of course there is the potential that it also breaks consumers that might be checking IList and IList<T> separately, although that probably doesn't matter much.

The language and CLR always permitted IList<T> to extend IList. I'd argue that the reason it doesn't is an explicit design choice, probably related to the obvious issues around variance. I don't think it makes much sense to change that, regardless of whether there are technical impediments for doing so.

VisualMelon commented 6 years ago

@HaloFour it doesn't violate any contract: if there was no A.DoStuff, then C would always have exposed B.DoStuff. When both are there, it is ambiguous, so ((C)thing).DoStuff() is illegal. If C explicitly hides A.DoStuff() then ((C)thing).DoStuff() could call B.DoStuff() (if the language decides this is OK, which I have been assuming but wouldn't necessarily support). C.DoStuff() doesn't exist, it is just A.DoStuff(), so doesn't need implementing. No members are 'removed' (only 'hidden', as per the OP), and nothing is left unimplemented. Adding this 'hiding' would not be a breaking change, because ((C)thing).DoStuff() was illegal before, would become legal, and wouldn't require any implementation.

DIM of course provides a way to do this shadowing, but it is different because it does (as you say) define a new contract as part of C. Adding this would also not be a code-breaking change, but it would enable the implementer of C to unilaterally change the behaviour (if the default implementation is not sealed), which is impossible if you hide A.DoStuff().

@DavidArno let's not give anyone any ideas! DIM should be OK if everyone takes it very seriously!

HaloFour commented 6 years ago

@VisualMelon

That doesn't sound like it offers anything over what you can already achieve today by having C.DoStuff shadow A.DoStuff and B.DoStuff. Any implementer of C is still responsible for implementing any combination of those methods.

let's not give anyone any ideas!

This is largely what DIM is for, so they already have those ideas.

VisualMelon commented 6 years ago

@HaloFour

That doesn't sound like it offers anything over what you can already achieve today by having C.DoStuff shadow A.DoStuff and B.DoStuff

I feel like I'm missing something: what do you mean by shadowing here?

This is largely what DIM is for, so they already have those ideas.

I'm sure this is just David and I having fun ;) DIM is (I hope!) for adding members to an existing interface which can be defined in terms of existing members of the same interface; I meant rather to suggest that while the example might look like fun (making IList<T> 'implement' IList), it and variations on it would not be good for anyone's health! (This change might not break any existing code - so might appear 'safe' - but itself breaks the contract on IList). I think David and I are of the same mind that DIM unlocks a number of terrifying doors: we just have to hope that nobody opens them! But this is all a bit tangential

HaloFour commented 6 years ago

@VisualMelon

interface A
{
    void DoStuff();
}

interface B
{
    void DoStuff();
}

interface C : A, B
{
    new void DoStuff();
}

public class D : C
{
    public void DoStuff() { } // could also explicitly implement any combination of A.DoStuff, B.DoStuff and C.DoStuff
}

static class Program
{
    static void Main()
    {
        C c = new D();
        c.DoStuff(); // just fine
    }
}
VisualMelon commented 6 years ago

@HaloFour that does indeed define a new contract, and is different because it defines a new contract. If C just hides A.DoStuff, then there is no C.DoStuff() to implement. This is precisely what hiding would offer. It offers C the chance to expose B.DoStuff() without requiring anything of the implementer, or even allowing the implementer to change it. new shadowing, or DIM shadowing both define a new method. new shadowing and an unsealed default method both give the implementer more power. Hiding A.DoStuff() wouldn't have these side-effects.

interface A
{
    void DoStuff();
}

interface B
{
    void DoStuff();
}

interface C : A, B
{
    // hide A.DoStuff
}

public class D : C
{
    void C.DoStuff() { } // error! CS0529: 'D.DoStuff()' in explicit interface declaration is not a member of interface
}

static void Main()
{
    C c =new D();
    c.DoStuff(); // calls D's implementation of B.DoStuff()
}
HaloFour commented 6 years ago

@VisualMelon

C already has DoStuff by virtue of extending either B or A. Shadowing doesn't change this anymore than some idea of hiding a member would.

This proposal has the exact same net effect as existing syntax. How you semantically consider the difference isn't relevant given that the responsibility of the implementers and the contract to consumer remains exactly the same.

VisualMelon commented 6 years ago

@HaloFour you can't call ((C)thing).DoStuff() at the moment, because it is ambiguous (CS0121 The call is ambiguous between the following methods or properties: 'A.DoStuff()' and 'B.DoStuff()'). In effect C does not have DoStuff(), though it can be projected as A or B which do have DoStuff().

Shadowing or hiding A.DoStuff() would allow you to call DoStuff() on C (((C)thing).DoStuff()_. I think we both agree that shadowing and hiding produce the effectively the same contract for the consumer, and you are absolutely right that the consumer shouldn't care about the implementation.

I do think there is value in considering how the implementer is affected: with hiding, they can't provide an alternative implementation for C.DoStuff(), which isn't the case with shadowing, which makes it easier and less error prone to implement C, the same as you can't provide an alternative definition for IReadOnlyList<T>.Count over IReadOnlyCollection<T>.Count. Conceptually C doesn't have DoStuff(), B does. Perhaps the upshot of all this is that allowing disambiguation by hiding is a bad idea ;)

HaloFour commented 6 years ago

@VisualMelon

Sure, the implementer wouldn't be able to provide an alternate implementation, but they were never required to in the first place. In the vast majority of cases the implementer would provide an implicit implementation that would magickally satisfy all three contracts. The implementer would only need to break out multiple (or explicit) implementations if the signatures conflicted in incompatible ways, such as differing return types or colliding generic types. But this proposal wouldn't alleviate that requirement.

So, this proposal solves no problems that can't already be solved today with very succinct syntax. DIM, which is coming to the language, introduces another way to solve the problem that can alleviate the burden on the implementer entirely. There's nothing here to justify yet another syntax. It's not less verbose and it's not functionally different.

VisualMelon commented 6 years ago

@HaloFour

So, this proposal solves no problems that can't already be solved today with very succinct syntax.

I never wished to suggest that the proposal is a good idea, but the ambiguous case is certainly an interesting one. Dismissing this case doesn't mean the proposal has no worth. The exposing of B.DoStuff() by hiding A.DoStuff() is not a part of the proposal: it is a decision that might reasonably be considered if the proposal were to progress.

It's not less verbose and it's not functionally different.

It is different from anything that can currently be achieved: it is different as far as the implementer is concerned if the implementer is providing explicit implementations. Sealed DIM is the only technique in the works that approximates this (though it doesn't perfectly, and is arguably less clear, because accessing B.DoStuff() from C would only be a syntax concern, not a semantics concern (consider pulling delegates from C/B) though arguably more clear because it is obvious what ((C)thing).DoStuff() is calling). It also prevents cluttering the interface definition, which is considerably more important than cluttering the code around it! It may be reasonable to argue that the difference is sufficiently small that it would be better to unify on a single method for 'redirection' to avoid the subtle differences creating confusion.

The implementer would only need to break out multiple (or explicit) implementations if the signatures conflicted in incompatible ways, such as differing return types or colliding generic types.

Just because A.DoStuff() and B.DoStuff() have exactly the same signature does not mean they are nominally the same: there is nothing preventing them having different purposes and implementations. I don't think it is ever reasonable to assume that a method will be implicitly implemented.

HaloFour commented 6 years ago

@VisualMelon

At worst an implementer would be forced to provide an explicit implementation of C.DoStuff in addition to the explicit implementations. I'd wager that's a rare case and not one that justifies language and CLR modifications to support.

CyrusNajmabadi commented 6 years ago

Sorry... i can't even figure out what the use case is for this. What problem are we trying to solve here, and why is it beneficial in real world scenarios?

I don't know if i've ever had an interface inheritance scenario where i wanted to hide something. If i really wanted to hide something i would:

  1. introduce a base interface without htat member, and then only add that member in one of the derived interfaces.
  2. not use inheritance and instead use composition with my implementation so that i was not pretending to be an instance of an interface when i really wasn't.
VisualMelon commented 6 years ago

@HaloFour I certainly can't think of any time I would want to use it: I think we can consider ourselves in agreement! As Cyrus says, if you are aggregating too many interfaces, there is probably something more important to deal with if you are contemplating artificially hiding a member.

MkazemAkhgary commented 6 years ago

you can take the same approach as .Net and don't implement IFoo from IFoo<T>. I made that mistake too. knowing when and how to write interfaces is challenge. if its not written for good purpose it just adds unnecessary complexity and providing no good use.

that's just my experience. funny thing is I'm also having difficulties related to interfaces in my current project. ive started to redesign some of my core interfaces and write them more carefully... pain is when you have to fix your code after you change interfaces!

Rand-Random commented 6 years ago

@VisualMelon @MkazemAkhgary @HaloFour @DavidArno @CyrusNajmabadi

First let me clear the point "Who would benefit from this?" or "I've never faced the problem."

If you ever, and I mean EVER, worked with generic and non-generic interfaces you have come across the problem, that I am suggesting could be solved.

Just imagine the simple case, this time I am using IFoo<T> and IFoo:

Today, you simply cannot make an inheritance of those two. Because, it would introduce the problem that IFoo<T> would "get" both methods of IFoo, you would end up with:

(I know the lines, of the non-generic IFoo won't be in IFoo<T>, I just simply put them there to simplify that they are inherited from IFoo)

public interface IFoo<T> : IFoo
{
    T Get();
    object Get();
    void DoStuff(T item);
    void DoStuff(object item);
}

As you may notice, T Get(); and object Get(); cannot co-exist since the signature is the same, so what you could do already to "hide" the non-generic method is apply the new keyword, and you would end up with: (as it is with IEnumerable<T> and IEnumerable with the GetEnumerator method)

public interface IFoo<T> : IFoo
{
    new T Get();
    void DoStuff(T item);
    void DoStuff(object item);
}

So far so good, but what about void DoStuff(T item); and void DoStuff(object item); the signature isn't the same so you cannot "hide" it with the keyword new.

Are we doomed for all eternity, to do as @MkazemAkhgary suggested it?

you can take the same approach as .Net and don't implement IFoo from IFoo.

Because, there was no solution in 2005, it’s the recommended way, don’t let generic interfaces inherit from non-generic interfaces?!?

I personally believe that is the wrong approach.

So, I ask you, why cannot we take the same approach as the keyword new does? Where is your problem there? Do you believe IEnumerable<T> inheriting from IEnumerable is a bad decision? As some of your comments implies:

@VisualMelon

I certainly can't think of any time I would want to use it: I think we can consider ourselves in agreement! As Cyrus says, if you are aggregating too many interfaces, there is probably something more important to deal with if you are contemplating artificially hiding a member.

@CyrusNajmabadi

Sorry... I can't even figure out what the use case is for this.

I don’t really see the difference between wanting to hide object Get() and void DoStuff(object item), maybe you can explain it to me, why you believe that the first is a good thing and the latter is a bad design decision?

In my opinion, maybe it’s to naive, I am simply suggesting a possibility to get rid of that limitation, and finally after more than 10 years allow generic interfaces inherit from its non-generic counter parts, by letting us hide the non-generic methods in the generic interface.

And just as simply applying the new keyword, it will change nothing from the implementation point of view.

So, my proposal once more is:

public interface IFoo<T> : IFoo
{
    new T Get();
    void DoStuff(T item); <--- modify this line with a keyword or whatever syntax, but it should hide the void DoStuff(object item) from IFoo as does the new keyword with the method above
}

Which would result in the following implementation:

public class Foo : IFoo<T> //don’t need to inherit from IFoo since IFoo<T> already does
{
    public T Get()
    {
        return default(T);
    }
    public void DoStuff(T item)
    {
        //do stuff
    }

    object IFoo.Get() //you are forced to explicitly implement this method since otherwise the signature would be the same as the generic part and they cannot co-exist
    {
        return this.Get();
    }
    void IFoo.DoStuff(object item) //maybe you are forced to explicitly implement it, the signature differs so it would compile to not do it explicitly
    {
        this.DoStuff((T)item);
    }
}

Which would result in the following behavior:

static class Program
{
    static void Main()
    {
        //foo is declared as Foo<T>
        Foo<T> foo = new Foo<T>();
        T t = foo.Get(); //can access the generic method since Foo<T> implements it
        foo.DoStuff(t); //can access the generic method since Foo<T> implements it
        object obj = foo.Get(); //won't call the non-generic method since Foo<T> implements object Get() explicitly
        foo.DoStuff(obj); //won't compile since Foo<T> implements void DoStuff(object obj) explicitly
        DoStuffWithFoo(foo); //will compile since Foo<T> inherits IFoo

        //foo is declared as IFoo<T>
        IFoo<T> foo = new Foo<T>();
        T t = foo.Get(); //can access the generic method since IFoo<T> declares it
        foo.DoStuff(t); //can access the generic method since IFoo<T> declares it
        object obj = foo.Get(); //won't call the non-generic method since IFoo<T> hides object Get() with the keyword new
        foo.DoStuff(obj); //won't compile since IFoo<T> hides void DoStuff(object obj) with ??? <- this is my proposal
        DoStuffWithFoo(foo); //will compile since IFoo<T> inherits IFoo <- this would be possible with my proposal

        //foo is declared as IFoo
        IFoo foo = new Foo<T>();
        T t = foo.Get(); //wont compile since IFoo doesn’t declare T Get()
        foo.DoStuff(t); //will call the non-generic method since IFoo declares void DoStuff(object item)
        object obj = foo.Get(); //will call the non-generic method since IFoo declares object Get()
        foo.DoStuff(obj); //will call the non-generic method since IFoo declares void DoStuff(object item)
        DoStuffWithFoo(foo); //will compile since foo is IFoo

    }

    public void DoStuffWithFoo(IFoo foo)
    {

    }
}
DavidArno commented 6 years ago

@Rand-Random,

You appear to be ignoring the elephant in the room here. For example, what you propose would lead to the below problem:

public interface IItemStore
{
    object GetItem();
    void SetItem(object item);
}

public interface IItemStore<T> : IItemStore
{
    T GetT();
    void SetT(T item);
}

public class Foo<T> : IItemStore<T>
{
    private object _item;

    void Set(object item) => _item = item;
    void SetT(T item) => _item = item;

    object Get() => _item;
    T GetT() => // what do we do here?
}

The reason why IFoo<T> commonly doesn't extend IFoo has nothing to do with hiding and/or explicit interface implementations. It is because of the challenge of how to implement a class that is expected to handle the passing in and out of values of any type (object) whilst simultaneously constraining those values to T.

If IList<T> implemented IList, how would you handle storing and retrieving non-T values via T-constrained methods? Clue: any answer involving throw or similar for when a value isn't of T would be a gross violation of the Liskov Substitution Principle, ie it would be a very bad solution. That's the solution that List uses and it is ugly, ugly, ugly. Your proposal would allow a way of forcing that ugliness on every implementation of IList<T>. No thank you.

Edited to correct mistakes pointed out by @Rand-Random

Rand-Random commented 6 years ago

@DavidArno Your code sample, doesnt use my proposal, so what problem about my proposal are you showing? So, dont really get what you are trying to show me, since the problem you are displaying has nothing to do with what I am proposing.

Also I dont get the sentence:

The reason why classes do not implement Foo and IFoo

Who is talking about a class implementing Foo and IFoo? If you meant to say,

The reason why classes do not implement IFoo<T> and IFoo

I just have to say, what? Sure clases implement IFoo<T> and IFoo, look at List<T>, look at Collection<T> and many more, thats basically the way to go that the class Foo implements both interfaces IFoo<T> and IFoo.

To this:

If List implemented List, how would you handle storing and retrieving non-T values via T-constrained methods?

Again, never said a word about a class implementing another class, I am solely talking about interfaces.

Clue: any answer involving throw or similar for when a value isn't of T would be a gross violation of the Liskov Substitution Principle, ie it would be a very bad solution.

If you, by any chance, are talking about this implementation of DoStuff from above:

    void IFoo.DoStuff(object item)
    {
        this.DoStuff((T)item);
    }

And you are complaining about that I am not manually checking if item is of T and just simply cast it directly, sorry. I was just lazy.

But, I can totally agree with you at one point, I am not seeing the "elephant" you are talking about.

DavidArno commented 6 years ago

@Rand-Random,

Apologies, I made a number of mistakes in my comment in my haste to post. I have corrected them.

The point being, it is a bad thing that List<T> implements List. But at least IList<T> doesn't extend it. So any implementation of IList<T> that I create can ignore List.

Your proposal suddenly opens the floodgates to bad design by providing a way of having generic interfaces extend non-generic ones, forcing LSP problems onto every implementation.

VisualMelon commented 6 years ago

I'll have to disagree with DavidArno that the proposal is inherently terrible (I just don't think it is very useful). The use-case presented (which is what DavidArno is really talking about) is, however, inherently terrible. I'm in complete agreement with DavidArno that List<T> implementing IList is horrendous (though I must admit that I've never noticed this!).

@Rand-Random

If you ever, and I mean EVER, worked with generic and non-generic interfaces you have come across the problem, that I am suggesting could be solved.

I have indeed done this many times. An interface should only extend another interface that it is completely compatible with, generics or no generics. If the other wasn't completely compatible, I wouldn't even contemplate implementing it. (As is the case with IList<T> and IList). There is no technical concern that stops IFoo<T> from extending IFoo. The problem is whether it makes any sense, and the reason IList<T> doesn't implement IList is because it doesn't make sense: it would break the contract on IList that says "I will accept any object".

All of this has nothing to do with C#, and has everything to do with a sound type system. The IFoo<T> and IFoo you describe both take values in, and return values. IFoo<T> is therefore invariant in T:i.e. if interface S extends R, you can't expose an IFoo<S> as an IFoo<R> or vice-versa. Your IFoo happens to be equivalent to an IFoo<T> where T is object. The same applies: you can't convert an IFoo<S> to an IFoo<object>.

(If you haven't already, I suggest you read this MSDN article about type variance and this MSDN article about generic type variance, which includes an example of why List<T> can't be converted to List<object>.

Do you believe IEnumerable inheriting from IEnumerable is a bad decision?

If IFoo<T> and IFoo only returned values of type T and object, then they could be covariant and would be conceptually compatible, because anything can be converted to object (define IFoo<out T> : IFoo), though it doesn't mean it is a good idea to actually impose the implementation. This is exactly why IEnumerable<T> can extend IEnumerable: it is covariant in T, they only return values of a constrained type. (I think it is a good decision, it's just a shame that foreach is inherently broken)

The 'elephant' is that IList<T> breaks the contract on IList:

void IFoo.DoStuff(object item)
{
    this.DoStuff((T)item);
}

That is an unchecked cast: that can fail at runtime. IFoo says "I have a method called DoStuff which takes an object". IFoot<T> says "I have another method called DoStuff which takes something of type T". Your implementation says "if you give me an object through IFoo, I might just crash".

Whether being able to hide base interface members is a useful idea or not (my understanding of the proposal), your examples are not a use-case for it. If you must treat an IFoo<T> as an IFoo (e.g. interfacing with legacy code), then you have to make a design decision about how to handle the fact that IFoo<T> violates the contract on IFoo. You (the programmer, not the library) have to decide what to do when you receive a string instead of a T, both of which look like object. Your example will just crash, and that is fine if you have decided that is the right thing to do in the particular circumstances, but appending a clause to IFoo.DoStuff(object) which says "this may crash" would be a terrible decision by the author of IFoo. Unless IFoo was defined without any sensible guarantees (i.e. stuff we usually assume), then declaring IFoo<T> as an extention of IFoo would be a terrible decision by the author of IFoo. Anyone that implemented IFoo<T> would have to decide how 'best' to violate IFoo.

As has been said before, there is no technical reason why IFoo<T> need not implement IFoo, but there are some very good conceptual/design reasons.

ghost commented 6 years ago

@DavidArno

I once programmed something I don't remember well but it looked similar to (it was test for default value of some kind. for simplicity let's that value be Null):

interface INull : INullable
{
    bool IsNull<T>(T item)
        where T : INullable;
}

interface INullable
{
    bool IsNull();
}

interface INull<T> :
    INull
    where T :
    INullable
{
    bool IsNull(T item);
}

I am not sure whether it is 100% the same but I think that it also illustrates the problem. It is a bit strange that I can't use new bool IsNull(T item). I wanted to hide bool IsNull<T>(T item) from INull.

EDIT: The interfaces I wrote are designed in a wrong way. I can't remember the exact code I had problem with but I think that it illustrates the problem.

VisualMelon commented 6 years ago

@drozdekl perhaps I'm not seeing the applications, but I would have to question the value of the INull interface: how would you expect to use and implement it? INull<T> can obviously be passed around to allow generic methods to detect default values for a given type, but how and where is an instance of INull expected to work?

ghost commented 6 years ago

@VisualMelon

It was something like

class Abc : INull
{
    public bool IsNull<T>(T item) where T : INullable =>
        item.IsNull();

    public bool IsNull() =>
        true;
}

class Cde : INull<Abc>
{
    public bool IsNull(Abc item) =>
        item.IsNull();

    public bool IsNull<T>(T item) where T : INullable =>
        item.IsNull();

    public bool IsNull() =>
        true;
}

It was not about Null I just picked some well known name. I would also design it in other way now. I just wrote it to discussion. In code above it is strange that you can't hide method from base interface. I know that this is a bad design but are all similar cases designed wrong? That's another question.

VisualMelon commented 6 years ago

@drozdekl I must confess complete confusion. Does INullable have an IsNull() method? If so, presumably INull<T> is provided to override some default behaviour (which would sadly require runtime type checks in IsNull<T>(T)) for a particular type. However, if the type is known, why would anyone call IsNull<T>(T) on an INull<T>? I figure this is the idea, that you want to hide this method away... but if you never want to call it because it is dangerous, then it shouldn't be there is the first place.

Can you show a concrete example of a method that would consume an INull<T> as an INull?

Edit: deleted some stuff I realised made no sense

ghost commented 6 years ago

The idea was that 'INull' is a default value (it is an interface for a singleton) for 'T' and INull (it is also an interface for a singleton) is an ultimate default value for all 'INullable'. It was an idea from my project where I tried to take C# generics on its limits only to know what are the possibilities.

The thing I tried to show is that there probably are examples with generic constraints involved where there maybe are reasons to hide method from the base class. Maybe it is true that all possible cases are bad designed and it is correct that hiding from base interface is not permitted but the argument against is not that there is no way to implement the interface.

I don't write such code anymore so I probably can't tell you the use cases. I really just wanted to show you that there is C# valid code (even though bad designed) where the implementation exists and there are good reasons to hide member from base interface.

EDIT: I have found the school project I was talking about. I extracted the important parts and renamed the interfaces to make more sense:

interface Type
{
    // It is not possible to use Null<this>
    Null NullValue { get; }
}

interface Value<T>
    where T : Type
{
    T Type { get; }
    bool IsNull();
}

// Null : Value<T> over all types T is not possible
interface Null
{
    bool IsNull<T>(Value<T> value)
        where T : Type;
}

// this is a simulation for Null : Value<T> over all types T
interface Null<T> :
    Null, Value<T>
    where T : Type
{
    bool IsNull(Value<T> value);
}
theunrepentantgeek commented 6 years ago

If you ever, and I mean EVER, worked with generic and non-generic interfaces you have come across the problem, that I am suggesting could be solved.

I frequently define related interfaces in the style: IBaz and IBaz<T>: IBaz so that I can aggregate together many different kinds of IBaz<T> in a single List<IBaz> or Dictionary<T, IBaz>. (For just one example, this approach is very useful when defining a rules engine as it allows specific rules to be strongly typed.)

I don't think I have EVER, and I mean not even once, run into a situation where hiding an interface method would have been desirable.

For the case where you are in control of the interfaces, and assuming you don't simply redesign your way out of the hole, the proposal for default interface methods would allow your interface implementations to be simplified in a useful way. See especially the [section on overrides in interfaces.

Rand-Random commented 6 years ago

@theunrepentantgeek

I don't thing I have EVER, and I mean not even once, run into a situation where hiding an interface method would have been desirable.

Let me ask you, how would you than implement a method that takes T as a parameter and also want to apply the non generic counter part?

For example with your Dictionary<T, IBaz>:

static class Program
{
    private Dictionary<Type, IBaz> _dict = new Dictionary<Type, IBaz>();
    static void Main()
    {
        object obj = DateTime.Now; //some random type
        IBaz baz = GetBaz(obj);
        baz.DoStuff(obj);

        IBaz<string> baz = GetBaz<string>();
        baz.DoStuff(obj); //<- how to prevent this line of code from compiling? since today in my opinion that is impossible if you work with interfaces, but can be achieved through explicit implementation on class level, why not on interface level?
    }

    private IBaz GetBaz(object obj)
    {
        return _dict[obj.GetType()];
    }

    private IBaz<T> GetBaz<T>()
    {
        return (IBaz<T>)_dict[typeof(T)];
    }
}

I didnt provide IBaz<T> and IBaz on purpose since I want to see yours.

theunrepentantgeek commented 6 years ago

I've hesitated to reply, @Rand-Random, because I'm pretty sure you won't like my answer - but leaving this hanging would be disrespectful to you, so here goes.

how would you than implement a method that takes T as a parameter and also want to apply the non generic counter part?

Simply put, I would not.

In my opinion, ending up in this situation is poor design and the solution is to improve the design.

If I saw code doing this in a code review, I'd reject the code, and then work with the author to identify a better way.

Rand-Random commented 6 years ago

@theunrepentantgeek

Thx, for the answer appreciate it, if I like the answer or not is irrelevant everyone has his/her opinion and can share it and I explicitly asked for your opinion,

but I must say, I am kind of glad that you (and others, since you aren't the only one with this opinion) weren't part of the .NET team when the decision about IListand IList<T> was made.

I am gratefull to the team who made List<T> inherit both IList and IList<T> and that it provides both method taking the corresposing paramters from IList and IList<T> (Add, Remove ...).

In my opinion it is usefull and I wouldnt want to miss it.

So, who ever did that, thanks for your poor design.

Going to keep it open, to give you a chance to reply, but I am gona close this issue sooner or later since I see no point in keeping it open any further.

Rand-Random commented 6 years ago

Thx everyone for the contribution.