Strawman proposal: Invokable objects (and faking two-phase lookup) #9400
Unanswered
sab39
asked this question in
Language Ideas
Replies: 1 comment 1 reply
-
I'm personally not a fan of how generics are proposed to work with this. It definitely feels like it exists very specifically to try to workaround the generic arity issue with generic methods but doesn't make a whole lot of sense on its own. |
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Rationale
Thinking about two-phase lookup in the context of extensions, it occurred to me that we might be able to fake it (at least for the most basic cases) with a variation on the same trick we've used since the inception of C# for faking named indexers.
The simplest version of the problem scenario in extensions looks something like:
People would like to be able to call this as
obj.DoThing<SomeType>()
, whereSomeType
isT2
, andT1
is inferred as the static type ofobj
. Today this doesn't work and you have to sayobj.DoThing<ObjType, SomeType>()
instead, which is counterintuitive and loses the benefit of inference.My thought is that we could fake the desired behavior, much like we fake named indexers today. C# doesn't support named indexers (
obj.Stuff[...]
) directly, only default indexers (obj[...]
). But if we make a property calledStuff
that returns a second type, and that type has a default indexer, then as far as consuming code is concerned, it looks and quacks an awful lot like a named indexer.In the same way
Stuff
is a property whose type behaves like an indexer, we'd makeDoThing
a property whose type behaves like a generic method group. That's not possible today, of course, but it occurred to me that we could make it possible with a fairly small and self-contained change to the language. Specifically, we could giveDoThing
's type anInvoke<T2>
method, and implicitly lowerobj.DoThing<SomeType>()
toobj.DoThing.Invoke<SomeType>()
.Note that it would still be up to the extension developer to implement the fakery; I'm definitely not suggesting that extensions would lower into this form automatically. Again, this mirrors the way named indexers are done.
After overthinking that idea for a while, here's what I ended up with.
Detailed proposal
If an expression
expr
resolves to a value of type F, and F is a non-delegate type with at least one accessible instance or extension method namedInvoke
, then:expr(
args)
is equivalent toexpr.Invoke(
args)
, including any inference of generic parameters that would be applied in the full form.expr<
TypeArgs>(
args)
is equivalent toexpr.Invoke<
TypeArgs>(
args)
.expr
can be target-typed or implicitly converted to any delegate type if the expressionexpr.Invoke
could be, with the same result, including generic inference.expr<
TypeArgs>
is allowed and can be target-typed or implicitly converted to any delegate type ifexpr.Invoke<
TypeArgs>
could be, with the same result. It doesn't have a natural type, even if the expanded form would be naturally typed as aFunc
orAction
.Considerations
Foo<T>()
between static methodsFoo<T>.Invoke()
andFoo.Invoke<T>()
. Of course we could specify rules for resolving such things, but I don't see any real benefit that would outweigh the complexity and potential confusion.Invoke
methods from this behavior, but that seems overly limiting when we're actively trying to expand the kinds of member that extensions can provide, so my proposal permits them.Thing<T>
and a propertyThing
with an instanceInvoke<T>
method were both in scope, making the expressionThing<T>
potentially ambiguous, but I think it's still okay:Thing<T>(
is currently illegal in existing code and would always refer to the method (except afternew
)Thing<T>.
would always refer to static members of the type; the new conversion can never apply in a situation that would be immediately followed by a.
. (This assumes the new conversion wouldn't be applied to receivers of extension methods, but I believe that's already the case for all implicit conversions anyway)nameof()
which would never apply the new conversion anyway.<
and>
as comparison operators. Hopefully the same rules that distinguish type parameters from comparison operators today can handle it.public void this() { ... }
to lower to anInvoke
method that's hidden from the language, as the nameItem
is hidden for indexers today.Beta Was this translation helpful? Give feedback.
All reactions