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.
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)?
@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.
I'd welcome any other solution for this specific problem that doesn't need any language change.
c#
await (obj as IMethod1Async)?.Method1Async() ?? Task.CompletedTask;
@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.
@alrz Given your last example, what happens when I call
```c#
await obj.Method1Async();
instead of
```c#
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.
@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.
@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)?
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.
@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?
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.