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: Optional methods #16064

Closed
alrz opened this issue Dec 22, 2016 · 10 comments
Closed

Proposal: Optional methods #16064

alrz opened this issue Dec 22, 2016 · 10 comments

Comments

@alrz
Copy link
Contributor

alrz commented Dec 22, 2016

Assume that we have the following base class with some virtual methods for derived classes to override.

abstract class Base
{    
    public virtual Task Method1Async() => Task.CompletedTask;
    public virtual Task Method2Async() => Task.CompletedTask;
    public virtual Task Method3Async() => Task.CompletedTask;
}

Base obj = new Derived();
await obj.Method1Async();
await obj.Method2Async();
await obj.Method3Async();

If we don't want to bother to call these methods if they weren't overridden, we should use interfaces,

class C : IMethod1Async, IMethod2Async, IMethod3Async
{    
    Task IMethod1Async.Method1Async() { .. }
    Task IMethod2Async.Method2Async() { .. }
    Task IMethod3Async.Method3Async() { .. }
}

var obj = new C();
await (obj as IMethod1Async)?.Method1Async();
await (obj as IMethod2Async)?.Method2Async();
await (obj as IMethod3Async)?.Method3Async();

Note that this won't work as await currently throws on null (#7171). So it'll be more verbose in practice.

Base obj = new Derived();
if (obj is IMethod1Async m1) await m1.Method1Async();
if (obj is IMethod2Async m2) await m2.Method2Async();
if (obj is IMethod3Async m3) await m3.Method3Async();

Swift has a concept of "optional methods" that may or may not be implemented in derived classes, e.g.

abstract class Base
{    
    public virtual Task Method1Async();
    public virtual Task Method2Async();
    public virtual Task Method3Async();
}

Base obj = new Derived();
await obj.Method1Async?();
await obj.Method2Async?();
await obj.Method3Async?();

Note: This is only possible in Swift's protocols. So in C# it may or may not be suitable in class declarations.

Task is just an example here, in any case if our virtual method is not empty, we should use similar patterns. I'd welcome any other solution for this specific problem that doesn't need any language change.

@svick
Copy link
Contributor

svick commented Dec 22, 2016

I'd welcome any other solution for this specific problem that doesn't need any language change.

What is the problem? What's wrong with the original code (using a default implementation in the base class)?

@alrz
Copy link
Contributor Author

alrz commented Dec 22, 2016

@svick I still need to call them all and take the overhead of Task for each method. and I won't know if they are actually overridden or not. In fact, I might need async/sync pair for each, calling all of those default methods is just unnecessary. Using an interface per method doesn't seem to be appropriate either.

@qrli
Copy link

qrli commented Dec 22, 2016

I'd welcome any other solution for this specific problem that doesn't need any language change.

await (obj as IMethod1Async)?.Method1Async() ?? Task.CompletedTask;

@alrz
Copy link
Contributor Author

alrz commented Dec 22, 2016

@qrli It's not any better than calling the virtual method with a default implementation that returns Task.CompletedTask. I want to avoid Task.CompletedTask and interfaces. In fact, you are just addressing #7171, my if workaround is slightly better than that but still it needs an interface per method.

@bondsbw
Copy link

bondsbw commented Dec 23, 2016

@alrz Given your last example, what happens when I call

await obj.Method1Async();

instead of

await obj.Method1Async?();

Can an optional method return anything other than Task (when async) or void (when not async)?

How about extending partial methods to allow public/internal/protected modifiers, implement polymorphism in the way you described, and allow them to be async with a Task return type? And no need for the ?() syntax.

@alrz
Copy link
Contributor Author

alrz commented Dec 23, 2016

@bondsbw Re "Given your last example, what happens when I call" Depending on the implementation, you probably cannot invoke it as a regular method. "Can an optional method return anything other than Task" why not? (Also, async is not part of the method signature). "How about extending partial" Partial method is a compile-time feature, but here we're operating on virtual dispatch, so that wouldn't be possible.

@bondsbw
Copy link

bondsbw commented Dec 23, 2016

@alrz

Depending on the implementation, you probably cannot invoke it as a regular method.

In that case, is there a need for the new invocation syntax?

"Can an optional method return anything other than Task" why not? (Also, async is not part of the method signature).

What would the returned value be if not implemented? default(T)?

Partial method is a compile-time feature

Sure, partial is currently a compile-time feature, but only because it is limited to scenarios where virtual dispatch is impossible. Is there a reason it couldn't be expanded to virtual dispatch scenarios (which don't also apply to your proposed syntax)?

@alrz
Copy link
Contributor Author

alrz commented Dec 23, 2016

In that case, is there a need for the new invocation syntax?

In Swift it's required probably because they need them to be visually distinguishable. And also, since return types vary (e.g. bool to bool?) I think that's consistent with nullability operators. ?., ??

What would the returned value be if not implemented?

default(T) or default(T?) in case of value types.

Sure, partial is currently a compile-time feature

I see, that's right. I didn't really thought about the syntax, a virtual method without a body just felt natural and that was the first thing I came up with.

@paulomorgado
Copy link

@alrz,

How would the compiler generate code without knowing what classes will derive from Base?

Can you post an example of how would this work for classes deriving from Base? And for classes deriving from those classes?

@alrz
Copy link
Contributor Author

alrz commented Dec 26, 2016

I explored some ways to implement this with interfaces (as mentioned in OP), a nullable delegate, a virtual property as condition, and template method pattern, it turns out that without clr support it wouldn't be that useful as a language feature and could be implemented via code generators.

@alrz alrz closed this as completed Dec 26, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants