Allow passing type name as a function parameter without typeof. #2804
Replies: 53 comments
-
I don't understand, why would a code generator be able to process |
Beta Was this translation helpful? Give feedback.
-
Because generator doesn't have type info on this identifier since it's only dealing with syntax trees. Consider parsing an expression. Some identifiers will represent variables while others are class names. Having a way to pass all expression parameters into a function that dynamically decides what to do with those makes possible to write code generators that would rather obtain type info dynamically than deal with clumsy and hard-to-implement semantics analysis. |
Beta Was this translation helpful? Give feedback.
-
Why not update the generator to have type info? |
Beta Was this translation helpful? Give feedback.
-
Roslyn semantics analysis is very sluggish compared to syntax. There is a faster alternative which is building project separately with MSBuild and extracting type info through reflection wherever changes occur but this entails quite an amount of work and support. |
Beta Was this translation helpful? Give feedback.
-
@edward-a If you want to, you can easily remove class RemoveTypeofVisitor : CSharpSyntaxRewriter
{
public override SyntaxNode VisitTypeOfExpression(TypeOfExpressionSyntax node)
=> node.Type;
}
…
new RemoveTypeofVisitor().Visit(node) The result of this will not be semantically correct, but I don't think that matters to you. If you use this to preprocess code with |
Beta Was this translation helpful? Give feedback.
-
The source code is an expression and has to be compilabe. E.g. a = b + Math.Abs(c). To obtain type info dynamically in the generated code I need to be able to make a call like G(b, Math, Math.Abs, c). Second parameter is the problem. |
Beta Was this translation helpful? Give feedback.
-
That's surprising to me. After all, semantic analysis needs to run in the sub-milliseconds range in order to support things like intellisense. |
Beta Was this translation helpful? Give feedback.
-
That would be the case with:
So have it be |
Beta Was this translation helpful? Give feedback.
-
It's super slow if you use from code. |
Beta Was this translation helpful? Give feedback.
-
Code generator doesn't know which member to wrap with typeof because it doesn't have type info from the semantic analysis. It's only dealing with expressions like a = b + Math.Abs(c) |
Beta Was this translation helpful? Give feedback.
-
Isn't the third parameter also a problem? For this code to compile, |
Beta Was this translation helpful? Give feedback.
-
I don't know what this means. I use Roslyn "from code" all the time, and i can do tons of semantic operations per second without any issue. |
Beta Was this translation helpful? Give feedback.
-
i don't see how this could work effectively for so many cases in C#. It's not just |
Beta Was this translation helpful? Give feedback.
-
I still need a good explanation of why using Roslyn to get this type information is out of scope. I just checked and i was able to do 1 million semantic model operations in roslyn in just over 2 seconds. This was on a file like: using System;
class C {
void Foo(int a, int b) {
var x = a + Math.Abs(b);
}
static void Main() { }
} |
Beta Was this translation helpful? Give feedback.
-
Yes, it's a also a problem. But this problem is solvable since we only care about function return type to be able to parse member accesses like A.B(C).D.E. And since code generator already knows from the syntax it's a method call, we can use the code below to wrap the function and obtain it's return type dynamically: class FuncWrapper<T>
{
public static FuncWrapper<T> Create(Func<T> f) {
return new FuncWrapper<T>();
}
}
class FuncWrapperFactory
{
public static FuncWrapper<T> Create<T>(Func<T> f) {
return new FuncWrapper<T>();
}
}
G(b, Math, FuncWrapperFactory.Create(() => System.Math.Abs(c)), c) |
Beta Was this translation helpful? Give feedback.
-
I'd generally agree, but it would be a source breaking change. For example: static class CallerHelpers
{
internal static void Call(Type callerType) { throw null; }
internal static void Call(Caller caller) { throw null; }
}
class CallerInfo
{
public Caller Caller { get; set; }
public void DoSomething()
{
CallerHelpers.Call(Caller);
}
} In this case, while contrived, CallerInfo.DoSomething can only call I've seen code not entirely unlike this in multiple codebases. |
Beta Was this translation helpful? Give feedback.
-
@FiniteReality, ah good point re breaking change conflicts like that. Oh well, back to the drawing board 😄 |
Beta Was this translation helpful? Give feedback.
-
I believe this gets resolved easily by making compiler choose the second method in such cases. Or am i missing something? |
Beta Was this translation helpful? Give feedback.
-
It complicates the implementation. For a proposal that basically benefits a single person writing a very niche tool that intentionally skips using the built-in components for syntax analysis it's already exceptionally unlikely to be considered. Upping the difficulty, by any degree, only further ensures this. |
Beta Was this translation helpful? Give feedback.
-
That is just not true. The syntax analysis components are fully utilized. But anyway, i suggest skipping on the OP's particular needs and review the syntax sugar benefits and complications. Updated the OP's post appropriately. |
Beta Was this translation helpful? Give feedback.
-
Eliminating 8 characters around a relatively uncommon operation doesn't seem like a particularly big benefit, especially since it involves the compiler having to interpret the token as potentially two different types of identifiers. Even eliminating the whole syntax analysis and source generator use case as described in the OP there's so little benefit here. |
Beta Was this translation helpful? Give feedback.
-
In most cases in generators, you know whether something is going to be used as a type or a variable anyway, since you're the one generating the code. I can't think of a case where this isn't the case, and even in generators which aren't based on Roslyn you could probably use some form of tagging to indicate whether a value is a type or not. |
Beta Was this translation helpful? Give feedback.
-
The generated code is based on expressions written in C#, so without semantics there is no way to statically resolve whether a type or variable is used for member accessing. It should be possible to resolve dynamically if the proposal goes through. |
Beta Was this translation helpful? Give feedback.
-
That would be a big change in behavior.
Can you give an example expression and how your generator interprets that to cause this ambiguity? |
Beta Was this translation helpful? Give feedback.
-
Can you elaborate on this? It doesn't seem that it changes the current behavior, although i might be missing something here.
The simplest example would be accessing static class member, e.g. Math.Abs(). There is no way to know if Math is a type or variable from syntax analysis. You could however figure this out if you were able to pass Math as an argument to a function and use reflection. |
Beta Was this translation helpful? Give feedback.
-
As stated, code currently can only pick the first overload. However, with the change you've suggested, that same code will suddenly pick the second overload on recompilation, which is s a change in behavior and violates the Principle of Least Surprise. |
Beta Was this translation helpful? Give feedback.
-
I don't see it picking up the first overload, it's definitively the second overload that gets called in DoSomething. static class CallerHelpers
{
internal static void Call(Type callerType) { throw null; }
internal static void Call(Caller caller) { throw null; }
}
class CallerInfo
{
public Caller Caller { get; set; }
public void DoSomething()
{
CallerHelpers.Call(Caller);
}
} Implementing the proposal and making compiler favor the second call over first doesn't seem to change current behavior. |
Beta Was this translation helpful? Give feedback.
-
Oh, that's my bad; I thought |
Beta Was this translation helpful? Give feedback.
-
I would enjoy this as well, many times I have methods that take in a string as a "nameof" that they want to receive, but I have no way to enforce that. I would simply expect the syntax to be a variable that is coalesced to a stringifiable variable via nameof, automatically, but still retains the type safety if needed. IE I may still want to specify "I will want the name of this variable, but that variable still needs to be an int!"
Something like this. |
Beta Was this translation helpful? Give feedback.
-
@SteffenBlake that sounds more like #287 to me. |
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.
-
E.g.
Why it's needed:
Dart has this implemented and it comes very handy when writing code generators.
Update: As some have pointed out, the proposal may too much concentrate on the particular needs of OP. The suggestions is to focus on the syntax sugar part.
Beta Was this translation helpful? Give feedback.
All reactions