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

Use lambdas to implement interfaces and abstracts classes that have a single abstract method. #4801

Closed
jmaine opened this issue Aug 26, 2015 · 11 comments
Labels
Area-Language Design Resolution-Won't Fix A real bug, but Triage feels that the issue is not impactful enough to spend time on.

Comments

@jmaine
Copy link

jmaine commented Aug 26, 2015

Use lambda's to implement interfaces and abstract classes that have a single abstract method defined.

Example interface.

interface Interface {
     Method()
}

Example method declaration:

void Call(Interface i) {
 //do work
}

Using a lambda to implement it.

obj.Call(() => { /* work } );
@jmaine jmaine changed the title Use lambda's to implement interfaces and abstracts classes that have a single abstract method. Use lambdas to implement interfaces and abstracts classes that have a single abstract method. Aug 26, 2015
@svick
Copy link
Contributor

svick commented Aug 26, 2015

What's the point? You can already use a delegate type for that.

And if you really want to have an interface, you can use both, see for example the Comparsison<T> delegate type and the equivalent IComparer<T> interface.

@dsaf
Copy link

dsaf commented Aug 27, 2015

I see some level of similarity with #13.

obj.Call(new { Method = () => { /* work */ } });

@dsaf
Copy link

dsaf commented Aug 27, 2015

What happens if an abstract class has a private/protected field/property/method - will it be possible to access this?

@gafter
Copy link
Member

gafter commented Sep 13, 2015

@jmaine Can you provide some motivation for why this change would be worth taking in the language?

Changing the lambda conversion like this would be a breaking change.

@gafter gafter added the Resolution-Won't Fix A real bug, but Triage feels that the issue is not impactful enough to spend time on. label Sep 13, 2015
@jmaine
Copy link
Author

jmaine commented Sep 14, 2015

Why would it be a breaking change?

@gafter
Copy link
Member

gafter commented Sep 14, 2015

@jmaine because there are existing types that are single-method interfaces and abstract classes. Therefore we would be adding a conversion that did not exist to existing code. That may make some methods that were previously inapplicable applicable. That could make some method calls that used to work fine into ambiguity errors.

For example

public interface ISAM { void M(); }
public delegate void DEL();

public class Zippy
{
    public static void Pinhead(ISAM sam) { }
    public static void Pinhead(DEL del) { }
}

class Client
{
    public static void Main(string[] args)
    {
        Zippy.Pinhead( () => {} );
    }
}

This is code that would compile and run just fine today, but under the proposed change it would be ambiguous (an error) because both Pinhead methods are applicable, but neither is more specific.

My question remains: what is your motivation for this change? How would it improve the plight of programmers? Or are you just suggesting it because Java did things that way? When I proposed the addition of lambda expressions to Java I specified it this way* because, among other reasons, Java does not have the concept of delegate types. But C# certainly does have the concept of delegate types and so doesn't need this accommodation.

*I specified the lambda conversion for Java to single-method interfaces only, not abstract classes, but the reason for that is a longer story.

@gafter gafter closed this as completed Sep 30, 2015
@alrz
Copy link
Contributor

alrz commented Apr 21, 2016

@gafter

This can be useful for interfaces that might be implemented by classes (OOP) but you might prefer a concise syntax to inline the implementation (FP), so delegates are not an option here. Without this feature you might define two overloads and wrap the delegate in a special class which causes double invocation. Local classes (#9523) could be an alternative approach but still they are nothing like a single lambda expression. And with capture lists (#117), we can simply control captured state in a lambda expression. There would be no such feature in a local class. I'll also note that with #258 you can use this feature with more and more interfaces.

As for backward compat we can use an attribute like FunctionalInterface so it won't break existing code. Alternatively, we can just prefer the overload that takes a delegate which I think is a better solution.

// examples 

interface IIterable<out T> {
  IIterator<T> GetIterator();
}

interface IIterator<out T> {
  T? Next();
}

interface IHandler<in T> {
  bool Handle(T value);
}

// OOP style
class Node : IHandler<Message> { 
  Node() {
    // FP style
    RegisterHandler(arg => { ... });
  }  
}
// OOP
class List<T> : IIterable<T> { 
  // FP
  public IIterator<T> GetIterator() {
    return () => { ... };
  }
}

// inline an iterable and iterator!
Method(() => () => { ... });

Also, sometime a delegate doesn't make much sense,

public delegate bool Iterator<T>(out T value); 

Since C# is an object-oriented language in the first place (with functional features) this can be a useful addition to take advantage of best of the both worlds.

Can you please consider reopening this? Thanks!

@gafter
Copy link
Member

gafter commented Apr 22, 2016

The idea that the presence of an attribute in source would change the semantic meaning of an expression seems like a bad idea.

I am having a hard time imagining any places in my own code or code I've seen where this would improve things. As the original author of the "single method functional interfaces" approach for Java that ended up being taken into that language, I have a deep appreciation for its value in Java. I am just having a hard time seeing how any effort in that direction for C# would make anybody's life easier.

@alrz
Copy link
Contributor

alrz commented Apr 22, 2016

@gafter I think it would be a nice feature when you are mixing functional and OO paradigms (which seems to be the direction that C# is following), a delegate cannot be implemented with a class and an interface is not compatible with a lambda expression. Anywho, I think I've made my point, if you don't see any value in here, I should probably count on local classes in these situations. :)

@svick
Copy link
Contributor

svick commented Apr 22, 2016

@alrz

a delegate cannot be implemented with a class

You can't do Comparison<int> c = new MyComparisonClass();, true. But using method group to delegate conversion is almost as good: Comparison<int> c = new MyComparisonClass().Compare;.

@alrz
Copy link
Contributor

alrz commented Apr 22, 2016

@svick My use case is the other way around, when you have an interface you will have to implement it. there is no way to work this around. I've seen classes like DelegateWhatever created just to implement an interface via a delegate. For example,

// this can't be a delegate because you might need to implement this
interface IHandler<in T> { bool Handle(T value); }
// (1) boilerplate class
class DelegateHandler<T> : IHandler<T> {
  readonly Func<bool, T> handler;
  public DelegateHandler(Func<bool, T> handler) { this.handler = handler; }
  // (2) double-invocation
  bool IHandler<T>.Handle(T value) => handler(value);
}

void RegisterHandler<T>(IHandler<T> handler) { ... }
// (3) additional overload
void RegisterHandler<T>(Func<T, bool> handler) => RegisterHandler(new DelegateHandler<T>(handler));

A lambda expression, specially in return would be bloody concise compared to this, specifically when you need to do this just once (so there would be no "helper overload"),

IIterator<T> GetIterator() {
  return () => { ... }; // YAY
}

After method references, this is the second feature that I missed from Java.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Language Design Resolution-Won't Fix A real bug, but Triage feels that the issue is not impactful enough to spend time on.
Projects
None yet
Development

No branches or pull requests

6 participants