dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
19.04k stars 4.03k forks source link

Proposal: C# interface delegation #13952

Closed Opiumtm closed 7 years ago

Opiumtm commented 8 years ago

There was useful feature in Borland Delphi, interface delegation. This feature is absent in C#. You can delegate interface implementation on class to instance stored in field or property.

Declare sample interface and sample implementation:

public interface ISampleInterface
{
    void DoSomeWork();
    int GetSomeResult();
}

public sealed class SampleImplementation : ISampleInterface
{
    public void DoSomeWork()
    {
        // do some work
    }

   public int GetSomeResult()
   {
       // return some result here
   }
}

Delegate interface implementation:

public sealed class DelegatedImplementation : ISampleInterface
{
    private ISampleInterface _wrappedObj : ISampleInterface;

    public DelegatedImplementation(ISampleInterface wrappedObj)
    {
        _wrappedObj = wrappedObj;
    }
}

This would translate to:

public sealed class DelegatedImplementation : ISampleInterface
{
    private ISampleInterface _wrappedObj;

    void ISampleInterface.DoSomeWork()
    {
        _wrappedObj.DoSomeWork();
    }

    int ISampleInterface.GetSomeResult()
    {
        return _wrappedObj.GetSomeResult();
    }

    public DelegatedImplementation(ISampleInterface wrappedObj)
    {
        _wrappedObj = wrappedObj;
    }
}

So, ISampleInterface implementation is transparently delegated to field _wrappedObj

private ISampleInterface _wrappedObj : ISampleInterface;

Class, of course, can override this behavior:

public sealed class DelegatedImplementation : ISampleInterface
{
    private ISampleInterface _wrappedObj : ISampleInterface;

    /* delegation for DoSomeWork member is ignored as class explicitly implement this member */
    void ISampleInterface.DoSomeWork()
    {        
        // do some other work
    }

    public DelegatedImplementation(ISampleInterface wrappedObj)
    {
        _wrappedObj = wrappedObj;
    }
}

This would translate to:

public sealed class DelegatedImplementation : ISampleInterface
{
    private ISampleInterface _wrappedObj;

    /* delegation for DoSomeWork member is ignored as class explicitly implement this member */
    void ISampleInterface.DoSomeWork()
    {
        // do some other work
    }

    /* this member is still delegated as class doesn't override delegation behavior */
    int ISampleInterface.GetSomeResult()
    {
        return _wrappedObj.GetSomeResult();
    }

    public DelegatedImplementation(ISampleInterface wrappedObj)
    {
        _wrappedObj = wrappedObj;
    }
}
HaloFour commented 8 years ago

This could be implemented via source generators without requiring additional modifications to the language.

Opiumtm commented 8 years ago

@HaloFour you can implement anything via source generators. To avoid usage of source generators language features are introduced.

HaloFour commented 8 years ago

@Opiumtm

you can implement anything via source generators.

To a degree, yes. However, source generators would work particularly well as there are no new grammatic structures that need to be added, just boilerplate code.

To avoid usage of source generators language features are introduced.

Language features are expensive in terms of implementation and support. There's no reason modify the language itself in order to handle something that can be easily handled via an outside tool, such as source generators. Source generators would also give you flexibility in how these things are implemented and promote a variety of solutions, whereas a language feature would lock you into whatever the language design team deemed the appropriate solution. Not to mention, source generators are actually likely to happen in the relatively short term, whereas a feature like this is likely going to take a backseat to the long list of things that the team has expressed interest in actually doing, assuming it would be considered at all.

Opiumtm commented 8 years ago

@HaloFour no, you are wrong Interface delegation is a quite common task and Borland Delphi (aka Object Pascal) language have this feature. Code generator is unreliable and clumsy solution. It require additional build step or manual run to make source up-to-date.

HaloFour commented 8 years ago

@Opiumtm

Code is unreliable and clumsy solution. It require additional build step and manual run to make source code up-to-date.

I'm referring specifically to the proposed Roslyn compiler feature Source Generators which seeks to add source generation directly into the compiler pipeline. There would be no additional build step nor manual tasks. All you would do is add the generator reference to the project, just like you might with an analyzer today.

Opiumtm commented 8 years ago

@HaloFour if you use custom source generator, it would be anyway clumsy to use especially if you publish your code as open-source. It would be very confusing for other developers who fork or just examine your lib to discover that your source code is generated automatically by tool. At first glance it would look like invalid code and maintainability would suffer. Source code generators are good for custom proxy generators, for serialization and so on. But use generator for regular development tasks is a bad idea.

DavidArno commented 8 years ago

@Opiumtm,

It is reasonable to assume that custom source generator would be packaged via nuget, thus their would be no confusion for devs forking your code as that nuget package would be pulled down and run automatically. This process works already for custom analyzers.

Opiumtm commented 8 years ago

@DavidArno not for regular development tasks like interface delegation.

HaloFour commented 8 years ago

@Opiumtm

not for regular development tasks like interface delegation.

Annotation-driven delegation is exactly how Groovy handles it.

Anyway, you're free to disagree. But source generators are actually going to happen. Check out #124.

Opiumtm commented 8 years ago

@HaloFour anyway interface delegation is a very simple language feature (it is very simple to understand and to implement and it only adds optional : ISomeInterface to field/property declaration - see code above), which was very useful in Object Pascal. This task in C# now require painful boilerplate code. Source code generators isn't a tool to implement this feature which was already introduced in many languages including ancient Object Pascal.

HaloFour commented 8 years ago

@Opiumtm

This task in C# now require painful boilerplate code.

Indeed, and the entire point of source generators is to produce painful boilerplate code.

already introduced in many languages including ancient Object Pascal.

The age of the language isn't relevant. C# is not Pascal, nor will it ever become Pascal. That's saying a bit considering that the person who designed much of Object Pascal also designed C#.

If you want Pascal on .NET, I suggest Oxygene.

dsaf commented 8 years ago

@Opiumtm This syntax in particular looks quite alien to C#:

private ISampleInterface _wrappedObj : ISampleInterface;

Maybe you can take some inspiration from Kotlin rather than Delphi and propose a new syntax? https://kotlinlang.org/docs/reference/delegation.html

I personally never used the pattern and don't see much benefit in baking it into language. Can you give a real-world example of a project where delegation would be used a lot?

Opiumtm commented 8 years ago

This syntax in particular looks quite alien to C#:

Not so alien. It is similar to how interface implementation is declared in classes.

private ISampleInterface _wrappedObj : ISampleInterface;

// it is very similar to
public class SomeClass : ISampleInterface
{
}

I personally never used the pattern and don't see much benefit in baking it into language. Can you give a real-world example of a project where delegation would be used a lot?

One example is a unit tests. Other example is when you want to implement some standard interface (like IEnumerable or IDictionary) on your class if you don't want to expose internal collection or dictionary as a read-only property. Most simple way to do it is to delegate interface implementation to internal collection.

DavidArno commented 8 years ago

@dsaf

I personally never used the pattern and don't see much benefit in baking it into language. Can you give a real-world example of a project where delegation would be used a lot?

I'd never heard of the delegation pattern, but reading your Kotlin link, I realise it's a pattern I use; I just didn't know its name.

I use it in situations where I eg want a Dictionary, with slightly different behaviour and I don't want to use inheritance. The downside is that many .NET interfaces have horrific numbers of methods and properties, resulting in lots of biolerplate code that just calls the underlying type.

The source generators support in a future C# release will help hugely with this, as a simpler generator could take a delegate class and populate all that boilerplate for me. This would be another nail in the inheritance coffin. However, like you and @HaloFour, I do not see this as something that would need baking into the language, when it would work just fine with source generators.

dsaf commented 8 years ago

@Opiumtm I meant it starts looking a bit like Scala/Swift/TypeScript in a confusing way:

def _wrappedObj : ISampleInterface;

I know it's a different language, but the pattern of putting the type on right side is too powerful to "spend" it on such a small feature as delegation. Developers are getting more polyglot these days.

@DavidArno yes, I realised this as well after @Opiumtm's comment.

Opiumtm commented 8 years ago

@dsaf It may be expressed lambda-like way

private ISampleInterface _wrappedObj => ISampleInterface;

Opiumtm commented 8 years ago

@DavidArno as I have some Borland Delphi background (before moving to C# in 2008 I have developed apps on Delphi), interface delegation language feature is well-known to me and it's indeed very useful and conceptually simple to understand and implement on language level.

vladd commented 8 years ago

Isn't the requested feature just a mixin in disguise?

DavidArno commented 8 years ago

@vladd,

I'd say no. A mixin is just a form of multiple inheritance, whereas a delegate is a wrapper on top of another type. Source generators will make it possible to add mixins to C# as well. I'm not sure that's such a good idea though.

AqlaSolutions commented 7 years ago

I need this! Why is it abandoned?

TomasJuocepis commented 7 years ago

I'd love to see this feature implemented. The only thing I would reconsider is the suggested syntax. I really like the syntax shown in the link provided by dsaf In one of the earlier comments:

Maybe you can take some inspiration from Kotlin rather than Delphi and propose a new syntax? https://kotlinlang.org/docs/reference/delegation.html

This is how the given example would look using that syntax:

//Delegate interface implementation:

public sealed class DelegatedImplementation : ISampleInterface by _wrappedObj
{
    private ISampleInterface _wrappedObj;

    public DelegatedImplementation(ISampleInterface wrappedObj)
    {
        _wrappedObj = wrappedObj;
    }
}
gafter commented 7 years ago

We are now taking language feature discussion on https://github.com/dotnet/csharplang for C# specific issues, https://github.com/dotnet/vblang for VB-specific features, and https://github.com/dotnet/csharplang for features that affect both languages.

The default interface implementation proposal (aka "traits") would support this use case. I will leave it as an exercise to the reader how, for now.

BBI-YggyKing commented 7 years ago

Another option to achieve (something similar to) this would be in the IDE - Visual Studio can already help you implement an interface on your class. Visual Studio could be extended to give you the option of implementing the interface by delegating to a field that implements the interface. You would still have the boilerplate interface delegation code, but at least you wouldn't have to write it yourself.

EDIT: As of November 1, 2017, it appears that Visual Studio 2015 now has this capability. Must have been added during an update? Or else I just never noticed before.

jeme commented 7 years ago

@bbi-yggy > Resharper can do that: Generate -> Delegating Members...

It's still code you need to maintain though as you mention.

DavidArno commented 7 years ago

@jeme,

Do you have a link to how that feature works, please?

jeme commented 7 years ago

@DavidArno https://www.jetbrains.com/help/resharper/Code_Generation__Delegating_Members.html

CyrusNajmabadi commented 7 years ago

Resharper can do that: Generate -> Delegating Members...

As can Roslyn (i added it when we first did Roslyn 'implement interface'):

image

ILAgent commented 6 years ago

Kotlin has that feature, and it would be greate if c# also had

dyarosla commented 4 years ago

The default interface implementation proposal (aka "traits") would support this use case. I will leave it as an exercise to the reader how, for now.

Respectfully, that proposal completely misses the mark on the value of interface delegation. The point of interface delegation is to move towards ‘prefer composition over inheritance’. Default implementations force coupling/reliance on those concrete implementations, whereas interface delegation stays decoupled from any implementation and only cares about the interface itself (and not in the interfaces that contain implementations sense of the word).

With default implementations you’re essentially creating a pseudo inheritance structure for interfaces and moving the opposite direction of ‘composition over inheritance’

StevenTCramer commented 2 years ago

@HaloFour

This could be implemented via source generators without requiring additional modifications to the language.

Has a Source Code Generator for this been implemented?

heinrich-ulbricht commented 9 months ago

Over the years I keep coming back to this issue, every time I need this Delphi-style interface delegation. I miss it.

penenkel commented 9 months ago

Has a Source Code Generator for this been implemented?

The only project that I know is: https://github.com/beakona/AutoInterface

ActivistInvestor commented 7 months ago

The default interface implementation proposal (aka "traits") would support this use case. I will leave it as an exercise to the reader how, for now.

Where is the default interface implementation for IList<T> ?

If and only if you are the owner/provider of the interface, then yes. Otherwise, where you are not the owner/provider of the interface, then no, the above statement is not true.

Default interfaces is a botched concept to begin with.

Had the right course been taken (this proposal), then with nothing more than one line of code or one modifier,List<T> becomes the default interface implementation for IList<T>, and to make matters worse, the implications of implementing this proposal would permit there to be multiple 'default interface implementations' for a single interface.

Coupled with the confusion that underlies default interface implementations (interfaces that derive from an interface with a default implementation do not inherit the default implementation), and I would have no choice but to conclude that default interfaces was clearly the wrong choice, and this proposal was the more-correct path.

Yes, I'm another former Delphi hacker (that is not interested in using Oxygene, because real-world constraints, rather than personal preference, limits my choice of language).

CyrusNajmabadi commented 7 months ago

Had the right course been taken (this proposal), then with nothing more than one line of code or one modifier,List becomes the default interface implementation for IList

@ActivistInvestor As mentioned already several times in the thread, you can accomplish this (also with one line) just with a source-generator. No need for a language feature to inform the compiler to spit out that code for you when you can already do that today. Note: such a source-generator feature would then work on any version of the language, and would work on any compiler supporting source-generators. No need to wait for some hypthetical C# vN for this to come out. You can just do this today.

ActivistInvestor commented 7 months ago

Source generation needs a trigger that tells the generator what member variable provides the implementation of an interface.

If it were implemented as a language feature the trigger would be a keyword, as it is in Delphi and other languages that support the feature.

What has also been mentioned in this discussion multiple times is pushback against using source generators verses a baked-in language feature.

I don't think I'm the only one that seems to be disenchanted by routinely getting what appears to be evolving into a defacto universal response to feature requests (source generators).

Source generators are baggage that most would rather not have to deal with, and I would venture to Guess that most would not be interested in writing source code that has a dependency on them.

HaloFour commented 7 months ago

Source generation needs a trigger that tells the generator what member variable provides the implementation of an interface.

Custom attributes work well in this kind of scenario.

I don't think I'm the only one that seems to be disenchanted by routinely getting what appears to be evolving into a defacto universal response to feature requests (source generators).

Source generators exist precisely for these kinds of problems. They enable developers to solve the problems for themselves instead of relying on the language team to solve all of the problems for them. The language team doesn't have the bandwidth or interest to solve all of these kinds of problems.

You not wanting to use a potential solution is not reason enough for the language team to take up a proposal like this. The choice will ultimately be between source generation, or no solution at all.

AqlaSolutions commented 7 months ago

@HaloFour, since source generators don't support chaining, they can't be relied on. The more generators you use, the more problems you have when some features don't work because one generator needs output of another. So having a possibility to do something with generators currently shouldn't be used as an argument against including the feature in the language itself.

HaloFour commented 7 months ago

@AqlaSolutions

Nor is not wanting to use source generators reason enough for including the feature in the language itself. Ultimately the language designers have not expressed any interest in this space.

Also, this repository isn't for language design discussions anymore, that is moved here: https://github.com/dotnet/csharplang/discussions/234

CyrusNajmabadi commented 7 months ago

Source generation needs a trigger that tells the generator what member variable provides the implementation of an interface.

Yes. A trivial attribute would work here. Just like a language feature would need a marker, you'd do something similar here.

If it were implemented as a language feature the trigger would be a keyword, as it is in Delphi and other languages that support the feature.

Precisely. So it would be a lot of work to get to virtually the same point as where you can get to today.

What has also been mentioned in this discussion multiple times is pushback against using source generators verses a baked-in language feature.

That pushback doesn't change the equation. I get you may not want to go this route. But the route still exists, and we're much less likely to invest work here when there are available options.

I don't think I'm the only one that seems to be disenchanted by routinely getting what appears to be evolving into a defacto universal response to feature requests (source generators).

It, like analyzers, are a defacto response when asking for a language or compiler feature that they are perfectly suitable for. Analyzers and generators are cheap. You could get a working option in an hour and could already be using it with any version of the language. Language features take years. If your disenchanted by us recommending ways to actually accomplish goals, I'm not sure what to tell you.

CyrusNajmabadi commented 7 months ago

So having a possibility to do something with generators currently shouldn't be used as an argument against including the feature in the language itself.

It definitely should. We're talking about several of orders of magnitude cheaper, and the ability for the feature to be concurrently designed and implemented by the community, instead of serially designed and implemented by us.

It would be a terrible waste of time and would harm all the other highly important features that cannot be already delivered in this fashion.

Finally, if you want this feature, then make a proposal discussion over at dotnet/csharplang. Roslyn is not the place for language requests.