dotnet / csharplang

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

Champion "default interface methods" (16.3, Core 3) #52

Open gafter opened 7 years ago

gafter commented 7 years ago

Open issues are being tracked at https://github.com/dotnet/csharplang/issues/406

LDM history:

scottdorman commented 7 years ago

Why are we limiting to only extend by allowing private and static methods? What about protected as well?

At Summit last year, @MadsTorgersen and I discussed how this could be used in the context of helping to properly implement IDisposable. From what I can remember of the discussion, and by way of a possible actual syntax, how about something like:

public interface IDisposable
{
        bool Disposed { get; protected set; }

        void Dispose() = 
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        };

        protected virtual void Dispose(bool disposing);
}

(Putting this here so it doesn't get lost. Pending the outcome of the question asked on https://github.com/dotnet/roslyn/issues/17054#issuecomment-278941593 I can repost the comment to the right place.)

DavidArno commented 7 years ago

The spec says there are no alternatives, but there is one: the delegation pattern as suggested in Roslyn issues [Proposal] Deligation class and Proposal: C# interface delegation.

In my opinion at least, the delegation pattern has far more merit than default interface methods:

  1. It requires no CLR change (it would be a pure syntax sugar language feature),
  2. It operates via composition, rather than inheritance, allowing extension of even sealed classes and structs.
  3. It avoids introducing multiple inheritance to the C# language.

Should I submit a PR to update the spec to reflect this alternative, or will you do it, @gafter?

gafter commented 7 years ago

@DavidArno Those "alternatives" do not address the principal motivation for the proposal, and are not, therefore, alternatives:

Virtual extension methods enable an API author to add methods to an interface in future versions without breaking source or binary compatibility with existing implementations of that interface.

gafter commented 7 years ago

@scottdorman protected would not make sense, as no class has an interface as its base class. Moreover, a method invoked from a class receiver (even the implicit this) is never an interface method (because a class does not inherit methods from the interfaces it implements). We'd have to reimagine the meaning of protected in some way to make sense of that, and I'd rather not do that before the value of doing so is clear.

scottdorman commented 7 years ago

@gafter I disagree that it doesn't make sense. Take the example of IDisposable. The full pattern is to include the protected virtual void Dispose(bool disposing) method, but since it's not part of the interface (because interfaces can't have protected members) it rarely gets implemented...which means that a lot of disposable classes end up being implemented the wrong way.

a class does not inherit methods from the interfaces it implements

This is true, but isn't this proposal sort of breaking that statement? We're basically saying "Implement this interface but you don't have to provide your own implementation for this interface method because the interface is already giving you one." You can then decide later on to implement the method yourself, in which case the default interface method is effectively ignored/overridden with the actual class implementation.

We'd have to reimagine the meaning of protected in some way

How so? This is simply saying that if you implement this interface there is a protected member that must be implemented. In that respect, it doesn't seem like it's any different than saying "The syntax for an interface is extended to permit private methods (the default access is public)", which is what we have in the proposal now.

DavidArno commented 7 years ago

Those "alternatives" do not address the principal motivation for the proposal, and are not, therefore, alternatives @gafter,

In that case I'm downvoting this proposal as it:

  1. Requires CLR changes
  2. Operates via inheritance, rather than composition so cannot be applied to sealed classes and structs.
  3. It introduces multiple inheritance to the C# language.

Since the underlying aim of this proposal can be achieved using the non-alternative delegation pattern approach, which doesn't suffer from any of the above problems, this doesn't seem a good proposal.

scottdorman commented 7 years ago

@DavidArno Given my earlier example of providing a default correct implementation of IDisposable.Dispose(), how would that be accomplished using your delegation pattern?

One of the goals of this proposal is:

Virtual extension methods enable an API author to add methods to an interface in future versions without breaking source or binary compatibility with existing implementations of that interface.

How would using your delegation pattern achieve that (in my earlier example) such that existing classes which already implement that member aren't broken but new classes can utilize the default implementation?

Just because a proposal requires CLR changes isn't a reason for it not to be done. If that were the case, there's a lot of language features we've gotten over the last several releases that wouldn't exist.

I don't see how this introduces multiple inheritance to the language. Yes, it provides a solution that other languages use multiple inheritance to solve, but it doesn't change the inheritance rules for C#. A class can still only inherit from a single base class and multiple interfaces.

scottdorman commented 7 years ago

I think Proposal: C# interface delegation is useful, would reduce a lot of boiler plate code that has to be written, and would love to see it in the language; however, I don't think it addresses the ability to provide a default implementation for an interface method or property nor does it extend the interface syntax to allow private methods, static methods, "override" methods (and hopefully protected methods).

[Proposal] Deligation class looks to achieve a similar result, albeit with a much uglier and more confusing (in my opinion) syntax so it wouldn't be able to achieve the goals set out in this proposal either.

gulshan commented 7 years ago

I think interfaces can just be allowed to have an unnamed nested class, that implements the interface. The nested class then can provide all the proposed features. Thus the main interfacing portion will not be polluted.

Also, how does this go with proposed extension everything proposal?

Thaina commented 7 years ago

This actually has alternative with generic extension method

I agree with @DavidArno

The proposal is conflict in itself. The purpose is to create function with concrete logic for interface. If it is concrete logic then it should not be overridden and we should separate some logic to be function that class derived from interface should implemented instead

iam3yal commented 7 years ago

@DavidArno

It introduces multiple inheritance to the C# language.

Well, it's not like we can't mimic this today and I don't think that multiple inheritance is bad per se because as far as I understand things like the diamond problem is going to result an error but I can certainly see that it might open a can of worms. :)

I voted for this because I think it can come in handy at times but I really like to see more love to composition too.

DavidArno commented 7 years ago

@scottdorman,

Given my earlier example of providing a default correct implementation of IDisposable.Dispose(), how would that be accomplished using your delegation pattern?

Since @gafter seems opposed to having protected members in an interface, this could be a moot point. However, rather than duck the issue with a technicality, I should answer your question. Something like the following could be used:

// built-in types
public interface IDisposable
{
    void Dispose();
}

public class Disposable : IDisposable
{
    private readonly Func<bool> _isDisposeNeeded;
    private readonly Action _dispose;

    public Disposable(Func<bool> isDisposeNeeded, Action dispose)
    {
        _isDisposeNeeded = isDisposeNeeded;
        _dispose = dispose;
    }

    public void Dispose()
    {
        if (_isDisposeNeeded())
        {
            _dispose();
            GC.SuppressFinalize(this);
        }         
    }
}

// my type
internal class MyDisposableType : delegate Disposable
{
    private bool _disposed;

    internal MyDisposableType() : delegate(() => !_disposed, PerformDispose) {}

    private void PerformDispose()
    {
        _disposed = true;
        // etc
    }
}

How would using your delegation pattern achieve that (in my earlier example) such that existing classes which already implement that member aren't broken but new classes can utilize the default implementation?

I wouldn't as that isn't necessary. As @Thaina says, this can already be achieved with extension methods.

DavidArno commented 7 years ago

@eyalsk,

If multiple inheritance is seen as no worse than single inheritance, then why not just allow full-blown multiple inheritance, rather than this "by the back door" partial approach?

iam3yal commented 7 years ago

@DavidArno Sure, I'd vote for it, haha... :)

I think that this solves a different problem though, even if we had MI in the language, it wouldn't solve the following problem:

Virtual extension methods enable an API author to add methods to an interface in future versions without breaking source or binary compatibility with existing implementations of that interface.

I've been asked many times before what's the difference between abstract classes and interfaces and my guess is that many people don't know the difference or they do but end up making the wrong choice and choose interfaces over abstract classes so in order to help these people a "plaster" is introduced into the language and I'm in need of one because I'm not perfect, yet. 😉

Now, I completely agree with you on the following part:

It also enables many programming patterns that require multiple inheritance without the issues of multiply inherited state.

This doesn't make a lot of sense because the next request would be to enable this for classes and maybe more specifically abstract classes, I mean why not? people are going to "abuse" interfaces just to achieve this so to me it makes more sense to add MI into the language at the same time.

It's a new feature so they can always throw an error at compile-time in cases it isn't "safe" such as in the diamond problem.

DavidArno commented 7 years ago

@eyalsk,

Am I missing something re,

Virtual extension methods enable an API author to add methods to an interface in future versions without breaking source or binary compatibility with existing implementations of that interface.

For, as in my reply to @scottdorman, extension methods surely already provide this?

Thaina commented 7 years ago

I think abstract is outdated. Object in the real world actually isn't hierarchy. Instead it is a composite of multiple aspect. Abstract really is just a class packed along with interface that it should be separated and composed

It just extension method was introduced so late so we stuck with abstract class to contain concrete method. Abstract class that need to derived to implement NotImplementedException such as stream is one example. Some stream can seek, some can't, but it must all be stream so it need to implemented like that

Which I think it's wrong. It should be explicitly state at compile time what it could do

For the virtual method. I want to explain that most of the time virtual method has concrete logic. And request you to always include base.ThatMethod() somewhere in the overridden method This also a disadvantage of inheritance. The implementor must know where should they put (or should not put) that base call so the logic still flowing as the api designer expect

Instead. Interface callback is more proper way to do thing like that. Because if logic really concrete and just want to allow some extension in the future. You should just made interface and extension method like a jigsaw that would be zigzag between concrete and extensible logic. So you can control where you think it should be really extended

static class Ext
{
    public static void DoSomething<T>(this T obj) where T : MyInterface
    {
         // startup
         obj.Before();

        if(obj.Predicate())
         // concrete logic a
        else // concrete logic b

         obj.After();
    }
}

public interface MyInterface
{
    void Before();
    bool Predicate{ get; }
    void After();
}

With this it more powerful to extend any class you want in any composite way you need than hierarchical that could be derived with only one linear inheritance

iam3yal commented 7 years ago

@DavidArno I think that you're looking at it from the wrong angle.

I don't think that the question should be whether extension methods CAN come in place of the "default interface methods" but whether they SHOULD and by that I mean sometimes there's functionality that NEEDS to be part of the interface but was missed from the design and it's too late to introduce new functionality or changes into the system so today people have two choices either to create a new interface or introduce a breaking change, both of these options might be bad, depends on your position.

Now, adding functionality through extension methods can solve the problem but sometimes it can be considered a hack to a faulty design, just as an extreme example extending a collection with a Add method through an extension method that by design needs to add stuff.

This is how I think about extension methods "if all you have is a hammer, everything looks like a nail".

rolfbjarne commented 7 years ago

@DavidArno

Re:

Am I missing something re,

Virtual extension methods enable an API author to add methods to an interface in future versions >>without breaking source or binary compatibility with existing implementations of that interface. For, as in my reply to @scottdorman, extension methods surely already provide this?

You can't provide a custom implementation of an extension method.

Example:

interface IA {
    public bool Something { get { return true; } }
}
class C : IA {
    public bool Something { get { return false; } }
}
iam3yal commented 7 years ago

@Thaina

I think abstract is outdated. Object in the real world actually isn't hierarchy. Instead it is a composite of multiple aspect. Abstract really is just a class packed along with interface that it should be separated and composed

Stating that abstract classes are dated is a bold statement to make.

In object-oriented hierarchies don't come from the object themselves but from the way we perceive reality and think about objects so it's really about the way we (humans) tend to organize information.

Objects aren't hierarchical but the relations between them might be for example your own family tree.

It just extension method was introduced so late so we stuck with abstract class to contain concrete method. Abstract class that need to derived to implement NotImplementedException such as stream is one example. Some stream can seek, some can't, but it must all be stream so it need to implemented like that

I think that you refer to NotSupportedException and if anything you need to blame the design and not the tool which btw given the circumstances is a reasonable design.

Extension methods wouldn't help here.

iam3yal commented 7 years ago

@rolfbjarne Yes, well, you can "extend" the object and "pretend" that somehow you extended the type. :)

DavidArno commented 7 years ago

@rolfbjarne,

I think you can. I haven't checked the code below, but I think it would work:

public interface IA
{
    bool Something { get; }
}

public static class IAExtentions
{
    public static bool SomethingElse(this IA a) => true; 
}

class C : IA
{
    public bool Something => false;
    public bool SomethingElse() => false;
}

The SomethineElse in C will override the extension method. Or am I wrong?

Of course, if the rules on where extension methods can be defined were relaxed, the interface could be modified to the following, to avoid creating a new type just for the extension, and without breaking existing implementations of IA (exactly as this proposal was aiming for):

public interface IA
{
    bool Something { get; }
    public static bool SomethingElse(this IA a) => true; 
}

But that may be taken the relaxation of the rules too far.

DavidArno commented 7 years ago

@eyalsk,

I may be wandering into the realms of "devils advocate" here, but taking the definition of the open/closed principle as ""software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification" (taken from Wikipedia), then surely using extension methods to extend an existing interface is exactly what we should do, rather than being a hack?

Thaina commented 7 years ago

@eyalsk

In object-oriented hierarchies don't come from the object themselves but from the way we perceive reality and think about objects so it's really about the way we (humans) tend to organize information.

It is just one perspective that we use to perceived a world. And which I was state, it outdated

Today there are many approach try to workaround to do composition object. Because that what we really use to construct object in factory. object is composed. Many times some object derived from something but lose some functionality. It does not hierarchy but mutation

Objects aren't hierarchical but the relations between them might be for example your own family tree.

My family tree is really great example. I may have something similar to my parents but I have many aspect lose from each of them. Object-oriented model hierarchy is wrong way to interpret real world. It can only has one parent and it can only expand

If I would represent my family tree I would use tree node, not object inheritance. I think relation is data. Not an object structure

Object that we made in programming also don't like a natural object. It mostly like artificial object that, as I said, construct from factory, which almost composed of various interface and module

if anything you need to blame the design and not the tool which btw given the circumstances is a reasonable design. Extension methods wouldn't help here.

That's why I don't said it bad. I just said it outdated. Because in the old day, that circumstance make an object-oriented hierarchy perspective most reasonable. But now we have better perspective and better tool we can revise that it have another approach which might be better

In my opinion Stream could be just interface, and might be many interface, IReadableStream, IWriteableStream, ISeekableStream. And all concrete logic of stream can ported to extension method of each respective interface. Instead of class Stream we would have IStream to wrap other object

interface IStream : IReadableStream,IWriteableStream,ISeekableStream{}

class MemoryStream : IStream { }

class HttpRequestStream : IWriteableStream { }

class HttpResponseStream : IReadableStream { }
iam3yal commented 7 years ago

@DavidArno

software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification

There are many interpretation to this principle, some even contradicts one another so let's stick to the wiki one: "entity can allow its behaviour to be extended without modifying its source code."

Now, you're right you can "extend" the object with new behaviour without ever touching the original class but it's important to note that you're not actually extending anything but adding new behaviour to existing objects, after all, extension methods are just static methods in disguises and we're basically passing objects to them so in some cases where things are dependent on the type it won't work, so here are two scenarios where it won't work:

  1. Let's say you're building a plugin system and you have two assemblies PluginEngine and PluginInterface, the former basically binds between the various components and creates the required objects that are used to extends the system whereas the latter is used by both the engine and plugin authors to implement the plugin.

    Let's say that you shipped a new version of the software and you want to add missing functionality to one of the interfaces in PluginInterface so you add it through extension methods but it wouldn't quite work because the engine won't pick them up so now, you need to come up with your own mechanism to workaround this limitation so you will need to modify the engine so it would understand that default methods exist in some other static classes.

    Let's say I'm a plugin author and I want to override one of the extension methods but I can't really do that so now the engine needs to be modified yet again to somehow allow plugin authors to override your default methods.

    With the default interface methods approach you will only need to update the PluginInterface and I may or may not update my plugin, ever and things will continue to work.

    Now, obviously, when the software is in development or fairly new you can change and break things with minimal costs but the moment it gained traction and you need to think about backward compatibility and still extend the software you can't introduce a breaking change.

    C# is not a new language, plugin engines were written with it and if the software has many plugins you wouldn't want to break anyone but you wouldn't want to be in a position that you can't extend the system either so this is just another approach to give you that option.

    tl;dr; may not work with reflection when a component tries to create objects solely based on interfaces and their implementations like in the case of plugin engines, IoC frameworks, etc..

  2. The using statement expects types to implement the IDisposable interface; adding a Dispose method through an extension method won't work.

Beyond these two technical points there are also authors of frameworks and libraries that want to deliver new functionality to their existing types without breaking anyone, just a matter of decision and with this proposal their consumers can override the default methods whenever they want at their own time.

then surely using extension methods to extend an existing interface is exactly what we should do, rather than being a hack?

Again, it really depends, if by design I have an interface that is missing core functionality then using extension methods to do that would be a "hack" because I can't touch the interface but if default interface methods would exist then I could add the functionality to the interface itself.

I guess that you're more pragmatic in the sense that it doesn't bother you that core functionality isn't part of the interface as long as you have access to it whereas in my mind extension methods are used to provide additional functionality on top of existing one, I'd usually put them in separate namespace and/or different assembly.

iam3yal commented 7 years ago

@Thaina

If I would represent my family tree I would use tree node, not object inheritance.

I didn't say you would use inheritance to implement it I just said that hierarchies exist in the relations that exist between objects but your nodes can actually be hierarchical for example FatherNode and MotherNode might have their gender fixed to male and female respectively where both of them may derive from PersonNode.

In my opinion Stream could be just interface, and might be many interface, IReadableStream, IWriteableStream, ISeekableStream. And all concrete logic of stream can ported to extension method of each respective interface. Instead of class Stream we would have IStream to wrap other object

You're right, it is an opinion and a choice but you can read about why they designed it the way they did here.

Thaina commented 7 years ago

@eyalsk

extension methods are just static methods in disguises and we're basically passing objects to them

I would like to argue that this is the underlying mechanism of class and object instance method anyway. Extension method just expose that underlying mechanism to the surface

This is the perspective that was actually blown my mind. Instance method is just actually function that always pass instance object as this. And it just that every object know it's own type. So it go referencing function from it type

How instance method actually differ from extension method?

And I think it is the core concept as golang. You can write new method for any class in golang like extension method. It very powerful and it actually work like charm

iam3yal commented 7 years ago

@Thaina

How instance method actually differ from extension method?

They differ in the rules that are applied to them for example instance methods can be virtual and as such calls to them are polymorphic whereas static methods cannot be made virtual and aren't subject to polymorphism.

Now, you copied only part of the sentence but my point above was that when things are dependent on the type then extension methods will fail to work in some circumstances because they operate on an instance of the type and aren't part of the type itself.

Just because an instance of the type is passed to an instance method at run-time doesn't mean that when you pass an object to a static method it makes it the same thing.

Thaina commented 7 years ago

@eyalsk And virtual method underlying is really a readonly function pointer. It just replaceable in the new class and that's all it is

Really, what I mean is mechanically it is the same. If it not virtual then it is a function. If it is virtual then it just wrap a function pointer

Being extension method mean it has no instance. It is static function in static class. And as we said from the start that what we need is "concrete logic on interface". And I think our "some circumstance" is not in this requirement

And also I wonder why logic for interface is not being concrete in your circumstance. Do you have scenario?

iam3yal commented 7 years ago

@Thaina I don't want to derail the post too much about technicalities I feel like we're going astray and I summarized everything I had to say in my post to @DavidArno. :)

rolfbjarne commented 7 years ago

@DavidArno, I think my example was a bit too simplistic :smile:

There are a few points here:

interface I {
    public int GetSomething () { return 0; }
}
class A : I { }
class B : I {
    public int GetSomething () { return 1; }
}
class C : I {
    public int GetSomething () { return 2; }
}
class Other {
    static void PrintSomething (I obj) {
        Console.WriteLine (obj.GetSomething ());
    }
    static void Main () {
        PrintSomething (new A ()); // this should print 0
        PrintSomething (new B ()); // this should print 1
        PrintSomething (new C ()); // this should print 2
    }
}
Thaina commented 7 years ago

@rolfbjarne

There is no such thing as extension properties

There is request about extension everything which was very well accepted. What left is just how syntax would be

Extension methods break polymorphism

I think it really actually honestly earnestly opposite. Extension methods on interface IS true polymorphism. You can have any concrete logic in extension method. And use interface as contract to apply to anything you like, because it is interface you can apply as many as you like to a class you made. You can derive another class to implement another interface to work with extension method for that interface

Whole System.Linq namespace is proof of the most powerful polymorphism in the whole C# library. Every kind of class implement IEnumerable<T> (and T can be any object) could use whole Linq namespace. Dictionary can iterate with select in the same manner as List without really deriving from the same superclass, just have another interface attach to it

I can't see how you'd implement the following using extension methods:

Because your sample is not really concrete logic for interface. What you actually want is, if it implement that interface, you will use its getsomething else you return 0

interface I {
    public int GetSomething();
}
class A { }
class B : I {
    public int GetSomething () { return 1; }
}
class C : I {
    public int GetSomething () { return 2; }
}

class Other {
    static void PrintSomething (object obj) {
        Console.WriteLine ((obj as I)?.GetSomething() ?? 0);
    }
    static void Main () {
        PrintSomething (new A ()); // this should print 0
        PrintSomething (new B ()); // this should print 1
        PrintSomething (new C ()); // this should print 2
    }
}

or if you want it constraint

public interface I { }
public interface ISomething : I { int GetSomething(); }

class A : I { }
class B : ISomething {
    public int GetSomething () { return 1; }
}
class C : ISomething {
    public int GetSomething () { return 2; }
}

static class Ext {
    static void PrintSomething(this I obj) {
        Console.WriteLine ((obj as ISomething)?.GetSomething() ?? 0);
    }
    static void Main () {
        new A().PrintSomething(); // this should print 0
        new B().PrintSomething(); // this should print 1
        new C().PrintSomething(); // this should print 2
    }
}
DavidArno commented 7 years ago

@rolfbjarne,

Ah, good point. I was forgetting that:

static void PrintSomething (I obj) {
    Console.WriteLine (obj.GetSomething ());
}

is effectively just syntactic sugar for:

static void PrintSomething (I obj) {
    Console.WriteLine (SomeExtensionClass.GetSomething(obj));
}

if GetSomething is defined as an extension method.

dmitriyse commented 7 years ago

Please consider a lightweight implementation as an intermediate stage of this feature See discussion here https://github.com/dotnet/csharplang/issues/222

dmitriyse commented 7 years ago

Making copy from #222 just to not forget about this scenario:

Default interface members can do great magic !!! with this feature we can finally get ICollection to be inherited from IReadOnlyCollection without breaking changes. (https://github.com/dotnet/corefx/issues/16626)

public interface ICollection<T>: IReadOnlyCollection<T>
{
     // no any member should be migrated to IReadOnlyCollection.
     // So we will have ICollection<T>.Count and IRedOnlyCollection<T>.Count.
}

public interface IReadOnlyCollection<T>
{
      // The same property is defined in ICollection<T>.
      // Default impelmentation will save as from breaking changes. 
      // Existing code that explicitly implements only ICollection<T>.Count 
      int Count 
      {
            get
            { 
                   // .this is threated by the compiler as System.Object.
                   var mutableCollection = this as ICollection<T>;
                   if (mutableCollection == null)
                   {
                         throw new NotImplementedException("You should add your implementation.");
                   }
                   return mutableCollection.Count;
            }
      }
}
yaakov-h commented 7 years ago

Virtual extension methods enable an API author to add methods to an interface in future versions without breaking source or binary compatibility with existing implementations of that interface.

If this is indeed the goal, the current proposal sounds a lot more like hacked-up multiple inheritance than interface forwards compatibility.

Has optional interface methods a la Objective-C been considered at all? Something like this would allow for new methods to be defined, and the caller would have to check to see if it's been implemented (possibly with a != null check, or reflection) before calling an optionally-defined method.

dmitriyse commented 7 years ago

You can vote to reopen #222 proposal, it's clearly define the goal. #222 is subset of #52, but probably goals are totally different.

yaakov-h commented 7 years ago

222 doesn't solve the case where you have a binary that implements the old interface (e.g. an extension or other loadable module) and calling code that knows about the new interface.

In #222, as far as I can tell, introducing a new method would require all code that implements it to be recompiled, which defeats the purpose of improving binary compatibility.

CLR-supported default interface methods, or optional methods, or probably a bunch of other solutions that have not been discussed - these would both address binary compatibility, not just source compatibility.

dmitriyse commented 7 years ago

222 doesn't solve the case where you have a binary that implements the old interface (e.g. an extension or other loadable module) and calling code that knows about the new interface.

Exactly for this case CLR change is required. CLR should detect that old binary misses new interface members implementation and generate this implementation according to defaults of new interface.

Initially in this proposal I loose this case and conclude that we can avoid CLR changes. Now I fixed this point.

dmitriyse commented 7 years ago

This CLR change should not be so hard. We already have code generation for generics, Default interface members generation for old binary code can be internally reuse generic method generation.

// Old version
public interface ISomeContract
{
     int MyProperty {get;}
     string MyFormatting{get;}
}

// New version (source code)
// ----------------------------------------------
public interface ISomeContract
{
     int MyProperty {get;}
     string MyFormatting{get;}

      string GetFormattedProperty()
      {
            return string.Format(MyFormatting, MyProperty);
      }
}
// --------------------------------------------

// New C# compiler generate the next assembly for the "new version"
// --------------------------------------------------
[DefaultImplementations(typeof(__ISomeContractDefaults))]
public interface ISomeContract
{
     int MyProperty {get;}
     string MyFormatting{get;}

      string GetFormattedProperty();
}

// Internally improved C# compiler can prepare helper class in the same assembly where ISomeContracts defined.
private static class __ISomeContractDefaults
{
       public static string GetFormattedProperty<T>(T __this) where T:ISomeContract
       {
             return string.Format(__this.MyFormatting, __this.MyProperty);
       }
}
// ----------------------------------------

// Old binary
public class SomeImpl: ISomeContract
{
       public int MyProperty {get;} = 5;
       public int MyFormatting {get;} = "MyProperty={0}";
}

// Some other assembly that links old binary and new contract binary
// -------------------------------------------
// Method in new binary, that knows about ISomeContract.GetFormattedProperty.
public void TestMethod(SomeImpl c)
{
       Console.WriteLine(c.GetFormattedProperty()); // MIssed implementation, currently Runtime failure.

       // IMPROVED CLR do this under the hood instead of failure.  
       Console.WriteLine(__ISomeContractDefaults.GetFormattedProperty(c));  
}
dmitriyse commented 7 years ago

One additional think about this like features: It should be implemented in every platform to which CoreFX targets (CoreCLR, .Net Framework, Mono, etc.). For example every CLR that wants to targets next platform (for example netcoreapp2.1/netstandard2.1) should implements this feature. Only with this guarantee CoreFX team can start to adopt this feature. So every CLR/Compiler change should have many stages: 1) Design 2) Implementation in all CLRs/Compilers 3) CoreFX code evolution.

gafter commented 7 years ago

Open issues are being tracked at #285

fschmied commented 7 years ago

As in https://github.com/dotnet/roslyn/issues/258#issuecomment-74222865, I'd once again like to challenge whether retaining binary compatibility after adding interface members should really be the "principal" motivation of this issue. (As a reaction to https://github.com/dotnet/csharplang/issues/52#issuecomment-279044731.)

To me, as an object-oriented programmer, a framework user, and a framework developer, the other motivation is at least as important, if not more so:

It also enables many programming patterns that require multiple inheritance without the issues of multiply inherited state.

Having a mixin-like mechanism, where customizable behavior can be added together with an interface contract, would be a great language feature that I would use often, in my regular every-day work. It would reduce boilerplate code, avoid awkward workarounds, and enable better designs. I tried to explain this in https://github.com/dotnet/roslyn/issues/258#issuecomment-74222865 and in https://github.com/dotnet/roslyn/issues/73.

Why's this important? Because the mixin-like mechanism does not require CLR support and might therefore have a lower bar regarding actual realization and inclusion in C# 7.1 or 8.0.

@gafter Could you comment on this?

gulshan commented 7 years ago

@fschmied #164 probably enables what you are talking about.

gafter commented 7 years ago

@fschmied I still think interface evolution is the principal motivation, but I'm glad the proposed mechanism also handles use cases you find more important.

gafter commented 7 years ago

Also, I would not want to rush the feature and get a suboptimal design that cannot be fixed later. I'd prefer we do it right.

alrz commented 7 years ago

Does the current implementation depends on a specific clr version?

guylando commented 7 years ago

How will this be used in methods which require dependency injection such as ILogger injection?

gafter commented 7 years ago

@guylando My naive reaction is that I don't expect it would change how dependency injection is done. Can you please elaborate on why it might?

HaloFour commented 7 years ago

@guylando

Where the method arguments themselves are injected? I assume that it wouldn't change anything; the implementation shouldn't know nor care where it's values are coming from.

guylando commented 7 years ago

We have the following situation: There are DAL classes per database table and they all inherit from BaseDAL and have they own interface for example IUsersDAL impemented by UsersDAL. BaseDAL implements IBaseDAL which has all select,update,delete methods implemented in the BaseDAL and IUserDAL inherits from IBaseDAL. There are some specific DAL classes which we now want to only allow select for them in a nice way other then overriding other methods with exception throwing. So we refactored IBaseDAL to inherit from new interfaces ISelectable, IDeletable, IUpdatable. And now classes can implement ISelectable instead of IBaseDAL by making IUserDAL inherit from ISelectableDAL instead of IBaseDAL.

THE PROBLEM IS that the UsersDAL class will still inherit from BaseDAL class and get all the delete, update methods. If other code will use the interface then the methods will not be visible but if some other code will use the class itself then the methods will be visible which is undesired.

So we wanted to spread BaseDAL to BaseSelectableDAL, BaseUpdatableDAL, ..etc but multiple inheritance is not allowed.

So the options we have currently are: 1)Create Base class for any combination of properties for example BaseSelectableUpdatableDAL, BaseSelectableDeletableDAL,... etc. 2)Create extension methods for the ISelectable, IUpdatable methods which will implement the select, update, methods and remove the methods declaration from the interface itself and remove inheritance from BaseDAL.

THE PROBLEM with the second solution is that the implementation requires dependency injection of the logger and other things which is not available in extension methods.

So then we found this thread about the default methods feature which can give solution to our problem by implementing logic as default methods of ISelectable, IUpdatable, etc . But this will be only possible if the default methods will have access to the dependency injection. Otherwise we are again left with similar problem to solution 2 above.