Allow type inference for class members #2227
Replies: 81 comments
-
How is the compiler supposed to know what delegate type to use for |
Beta Was this translation helpful? Give feedback.
-
I guess the same way it deduces the delegate type for a lambda? E.g. passing lambda as an argument in Class2.Create(this, () => this.b ? 10.0 : 20.0) |
Beta Was this translation helpful? Give feedback.
-
The compiler deduces the the delegate type for a lambda from the type it is converted to (e.g. a method parameter). But in the declaration of |
Beta Was this translation helpful? Give feedback.
-
The parameter of method is generic and also deduced by the compiler. |
Beta Was this translation helpful? Give feedback.
-
I have no idea what this refers to. |
Beta Was this translation helpful? Give feedback.
-
I updated the sample code to elaborate on what i mean. Notice that the version of the locally allocated delegate wrapper works just fine with type inference. It would be great to have it also work for class members. class Class1
{
bool b;
static FuncDelegate<double> F = FuncDelegate.Create((Class1 c) => { return c.f(); });
double f() { return this.b ? 10.0 : 20.0; }
public Class1()
{
Class2.Create(this, () => this.b ? 10.0 : 20.0); // Type inference but heap allocation ode delegate (assume hundreds of these)
Class2.Create(this, F.D); // No heap allocation but can't use type inference for class members
// Proposal:
// static var F = FuncDelegate.Create((Class1 c) => { return c.f(); });
// var f() { return this.b ? 10.0 : 20.0; }
// Compiler already can do that for local variables:
var f = FuncDelegate.Create((Class1 c) => { return c.f(); });
Class2.Create(this, f.D); // No heap allocation AND can use type inference!
}
}
class Class2
{
public static Class3<T> Create<T>(Class1 c, Func<T> f)
{
return new Class3<T>(c, f);
}
public static Class3<T> Create<T>(Class1 c, Func<Class1, T> f)
{
return new Class3<T>(c, f);
}
public class Class3<T>
{
public Class3(Class1 c, Func<T> f) { }
public Class3(Class1 c, Func<Class1, T> f) { }
}
}
class FuncDelegate
{
public static FuncDelegate<T> Create<T>(Func<Class1, T> D)
{
return new FuncDelegate<T>(D);
}
}
class FuncDelegate<T>
{
public Func<Class1, T> D;
public FuncDelegate(Func<Class1, T> D)
{
this.D = D;
}
}
` |
Beta Was this translation helpful? Give feedback.
-
I don't understand the point of your example. Why are you wrapping delegates with another class? Inference of members has been discussed to death. It's not worth the cost to implement: |
Beta Was this translation helpful? Give feedback.
-
I did it to use type inference that is already available in the compiler for local variables, but this time for a class member.
I my case, type inference for class members allows to get rid of delegate allocation for lambdas and use methods instead. This is critical as it allows optimization of parts that have many of lambdas passed as arguments and where the type inference is still needed. So, you believe that re-using lambda's return type inference for class methods to give that kind of flexibility in optimization is not worth it or too difficult to implement? |
Beta Was this translation helpful? Give feedback.
-
I think that you can't enable inference for a single scenario. The issues with member inference are due to the fact that, unlike locals, fields can forward reference. With lambdas it's even worse because a lambda has no type, the compiler must use the target type of the assignment to figure out the delegate type to use. There's nothing about the language today that forces you to incur additional allocations, you just have to be more explicit with your generic type arguments. |
Beta Was this translation helpful? Give feedback.
-
Well, i could be wrong, but doesn't a call like 'new a(() => this.b + this.c);' result in delegate allocation on heap?
The minimum needed to avoid delegate allocation in our case would be having 'var' as return type of a class method that i guess would be similar in type inference of lambda's result? Isn't it possible to only enable type inference for method's return type? |
Beta Was this translation helpful? Give feedback.
-
Delegate instances involve allocations. So do closures. What does that have to do with type inference?
This makes no sense. Type inference doesn't change what the code does. The only time
No. That method may be used by another method that infers the return type, and so on, and so on, and so on. |
Beta Was this translation helpful? Give feedback.
-
This particular example is not to demonstrate inference, but to get confirmation that delegate allocation occurs.
Type inference of method is needed to avoid delegate allocation in the constructor (see example in the beginning of this thread) and instead allocate it statically and use it as a proxy to call class method. When you have performance critical code with dozens of lambdas it pretty much makes sense to have means to reduce heap allocation.
That makes sense and i am not ready to present a counter-argument. |
Beta Was this translation helpful? Give feedback.
-
I don't understand this assertion at all, and I can't really make heads or tails of what your example is trying to demonstrate. That said, I think you misinterpret what the C# compiler is doing for your first example. It doesn't need to create an allocation for the lambda because you are not enclosing any additional state. You still incur the allocation for the delegate itself, but that's the cost of using delegates. |
Beta Was this translation helpful? Give feedback.
-
Let me summarize it for you:
Makes sense? |
Beta Was this translation helpful? Give feedback.
-
Not really, no. Type inference of members doesn't change how delegates or allocations work. |
Beta Was this translation helpful? Give feedback.
-
Thanks for the example, but i don't see a solution here the return type is still specified explicitly. Can you please elaborate? |
Beta Was this translation helpful? Give feedback.
-
It can be done with a static variable, it just requires you to explicitly state the type.
This is not going to change. As you acknowledged in your first post, this has been brought up before. Many times. The team simply doesn't consider it remotely worth the effort, and there are numerous situations where the inference explodes due to forward referencing as has been mentioned here. It's a very difficult problem with very little reward. I don't see anything new or unique about your situation that would warrant reevaluating that position, especially since supporting your type-less DSL is not a goal of the C# language and the number of people who would benefit is vanishingly small. |
Beta Was this translation helpful? Give feedback.
-
The solution is to specify the return type explicitly. C# has no way around this. And it's been a very well understood and intentional decision to not allow inference at the member level. It's incredibly thorny and problematic (something i can speak to at length given the work we did in TypeScript). The use case of "i want to have a dsl that is c#-like and would allow people to take pseudo-c# and paste it literally into c# and have it work without any augmentation" is a non-case. C# cares little for DSLs. It's fine with them existing, but the rule on them is: you need to translate your DSL into "legal" (i.e. 'correct') c#. C# does not go out of its way to make arbitrary DSLs, with arbitrary rules, work unchanged. As i mentioned above (with the RPN case), if i came to c# and said "my DSL allows for |
Beta Was this translation helpful? Give feedback.
-
That feature with method references keeps me thinking. If lambdas don't capture any local variables, then they can be compiled into methods that can be referenced without delegate allocation? |
Beta Was this translation helpful? Give feedback.
-
There have been discussions around this. The main problem here is that this could be an observable change in behavior. Consider, for example, someone who uses the delegate as a key into a dictionary. Whether or not you get a new instance or not could change behavior. There are also security concerns here (thoughthey may be moot depending on the direction .net goes in) as delegates capture information about the stack that was used to create them, and those can be used to make security decisions. I believe there's an open PR somewhere in Roslyn investigating this approach. However, i think it's been years with very little traction. So my guess is that it's not very palatable for the team. |
Beta Was this translation helpful? Give feedback.
-
You'd still need that initial allocation of the delegate, and you'd need to stick it somewhere that you can reference like a If you're asking if C# could selectively optimize the creation of certain kinds of delegates, yes it probably could, as long as there were no perceivable behavioral differences. Those requests can be raised on the Roslyn repo since they wouldn't involve any changes to the C# grammar to support. |
Beta Was this translation helpful? Give feedback.
-
The issues arising from having var as a return type of a method have already been mentioned in this thread. It's a good conversation to discover other solutions that might fit, such as in case of method references. For now, I can stay with the performance hit, it's an issue but not a show stopper. |
Beta Was this translation helpful? Give feedback.
-
Ah, here it is: dotnet/roslyn#6642 Looks like there's been some recent movement. So maybe we'll see something come of this this year. |
Beta Was this translation helpful? Give feedback.
-
Not sure i am following. Is my understanding correct that the bellow example doesn't ensue delegate allocation? public class Class1 {
public boolean b;
public double f() {
return this.b ? 10.0 : 20.0;
}
// the signature of ToDoubleFunction is: double f(T arg1)
public static ToDoubleFunction<Class1> F = Class1::f;
} If yes, when why a lambda that can be translates to the very same method by the compiler would result in delegate allocation? |
Beta Was this translation helpful? Give feedback.
-
Because C# has no concept of any other way to store a reference to a method. It's always in a delegate. |
Beta Was this translation helpful? Give feedback.
-
Then what are the benefits of introducing method references mentioned by @HaloFour |
Beta Was this translation helpful? Give feedback.
-
@edward-a I think there's a lot of confusion in this discussion. There seems to be a lot of conflation in your posts between what is actually possible in C# today versus what could potentially be possible with new language features. |
Beta Was this translation helpful? Give feedback.
-
Indeed, it's quite a brainstorming here and i appreciate all the input. Thank you for the link about the delegate caching. I am currently reading the thread struggling to understand the benefits of cached delegates. |
Beta Was this translation helpful? Give feedback.
-
The benefits of caching in this context are less allocations/garbage. |
Beta Was this translation helpful? Give feedback.
-
It sounds like you came up with a design that requires a change to the C# language. To be realistic, let's imagine that the language design team really loved this idea and got to work on it right away. You'd be able to use it in C# 9 or 10, potentially five years from now. What would you do in the meantime, and what's the difference between doing it for only five years or doing it for the life of the project? |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
@edward-a commented on Tue Feb 12 2019
I understand that using type inference for class members is a topic already raised in the past but here I want to show how the absence of it is highly detrimental to performance in some cases. In our code we have big auto-generated chunks with lots of lambdas passed as arguments. After removing all bottlenecks related to heap allocation, we are still stuck with delegates for lambdas heap-allocated upon instantiation of an object which significantly slows down the instantiation. We’d like to use class methods instead of lambdas called through static class methods to avoid the delegate allocations but in that case we would lose type inference which is no go for us. Please check the code below that presents the issue and the proposed solution.
Beta Was this translation helpful? Give feedback.
All reactions