Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: C# interface delegation #13952

Closed
Opiumtm opened this issue Sep 21, 2016 · 40 comments
Closed

Proposal: C# interface delegation #13952

Opiumtm opened this issue Sep 21, 2016 · 40 comments

Comments

@Opiumtm
Copy link

Opiumtm commented Sep 21, 2016

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
Copy link

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

@Opiumtm
Copy link
Author

Opiumtm commented Sep 21, 2016

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

@HaloFour
Copy link

@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
Copy link
Author

Opiumtm commented Sep 21, 2016

@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
Copy link

@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
Copy link
Author

Opiumtm commented Sep 21, 2016

@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
Copy link

@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
Copy link
Author

Opiumtm commented Sep 21, 2016

@DavidArno not for regular development tasks like interface delegation.

@HaloFour
Copy link

@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
Copy link
Author

Opiumtm commented Sep 21, 2016

@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
Copy link

@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
Copy link

dsaf commented Sep 22, 2016

@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
Copy link
Author

Opiumtm commented Sep 22, 2016

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
Copy link

DavidArno commented Sep 22, 2016

@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
Copy link

dsaf commented Sep 22, 2016

@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
Copy link
Author

Opiumtm commented Sep 22, 2016

@dsaf
It may be expressed lambda-like way

private ISampleInterface _wrappedObj => ISampleInterface;

@Opiumtm
Copy link
Author

Opiumtm commented Sep 22, 2016

@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
Copy link

vladd commented Sep 23, 2016

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

@DavidArno
Copy link

@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
Copy link

I need this! Why is it abandoned?

@TomasJuocepis
Copy link

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
Copy link
Member

gafter commented Mar 20, 2017

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.

@gafter gafter closed this as completed Mar 20, 2017
@BBI-YggyKing
Copy link

BBI-YggyKing commented Jun 7, 2017

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
Copy link

jeme commented Oct 17, 2017

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

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

@DavidArno
Copy link

@jeme,

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

@jeme
Copy link

jeme commented Oct 17, 2017

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

@CyrusNajmabadi
Copy link
Member

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

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

image

@ILAgent
Copy link

ILAgent commented Dec 14, 2017

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

@dyarosla
Copy link

dyarosla commented Mar 22, 2020

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
Copy link

@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
Copy link

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

@penenkel
Copy link

penenkel commented Feb 2, 2024

Has a Source Code Generator for this been implemented?

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

@ActivistInvestor
Copy link

ActivistInvestor commented Apr 8, 2024

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
Copy link
Member

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
Copy link

ActivistInvestor commented Apr 8, 2024

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
Copy link

HaloFour commented Apr 8, 2024

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
Copy link

AqlaSolutions commented Apr 8, 2024

@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
Copy link

HaloFour commented Apr 8, 2024

@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: dotnet/csharplang#234

@CyrusNajmabadi
Copy link
Member

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
Copy link
Member

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.

@dotnet dotnet locked and limited conversation to collaborators Apr 8, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests