Replies: 14 comments 4 replies
-
Doesn't the pattern proposal (along with assignments) solve this? |
Beta Was this translation helpful? Give feedback.
-
Doesn't the runtime/JIT already optimize the case when there's a check against the type of if (T is int i) // branch and other checks will be removed if T is a 'long'
{
//....
}
else if (T is long l) // branch and other checks will be removed if T is an 'int'
{
//....
}
else if (T is string s) // branches checking against value types are removed, checks against reference types and 'else' stay
{
//....
}
else // all other branches get removed if T is any other value type
{
//....
}
The methods don't, but the runtime does know. Your proposal seems based on a false assumption. The only thing that the language doesn't currently do is checking if |
Beta Was this translation helpful? Give feedback.
-
Whole idea of this proposal is to introduce additional generic type constraints in method code for some region of method code. If actual substituted type of if (T is int i) // branch and other checks will be removed if T is a 'long'
{
//....
} This wouldn't help as you cannot branch code for any struct nor add additional type constraints for generic type argument |
Beta Was this translation helpful? Give feedback.
-
Yes, so the problem is checking if So the concrete proposal is: Allow checking if a type parameter is any kind of |
Beta Was this translation helpful? Give feedback.
-
I'm curious about this "generic-yet-optimized-for-structs" method implementation. I'd think that such a beast would be relatively rare and not worth a language feature specifically to support. The normal case, as mentioned, would be to |
Beta Was this translation helpful? Give feedback.
-
I propose more broad approach. Ability to add any type constraints to existing and efficiently call appropriate "more constrained" generic methods. Part of the example: // "switch" statement on type argument
switch (T)
{
case struct T1:
OptimizedForStructs<T1>(value);
break;
// ...
} this part of code introduce "new" generic type argument Developer apparently can write additional private methods with more constrained (so, more specific) type arguments, but developer can't invoke those methods without slow and inefficient reflection. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour And I must note that such kind of optimizations is common for fundamental libraries such as serialization frameworks, generic collection libs, RPC network libs, object-relation-mappers and so on. Performance in such kind of libs is absolutely important and developer of such lib don't know exact types to be used with such fundamental lib. It maybe not so wide "developer base" for such libs, but it would be apparently wide "user base", so performance improvements in fundamental lib should be important for its user base. |
Beta Was this translation helpful? Give feedback.
-
Ah, I think I understand what you're getting at now. Outside of the public class C<T>
{
void Do(T value)
{
if (value is IAdditional a)
{
M(a);
}
}
void M<T1>(T1 value) where T1 : IAdditional
{
}
}
public interface IAdditional
{
} But it would only work as long as there's a single constraint. If At this point I'm inclined to say we've hit a "Gödel limit", so to speak. Which is to say, with the double constraint on |
Beta Was this translation helpful? Give feedback.
-
Yes, you understand it. It's what I'm talking about. |
Beta Was this translation helpful? Give feedback.
-
Then you realize that what you're asking for is roughly equivalent to solving the Halting Problem? Don't take it personal, I really hate to have rained on your parade, but having learned about Gödel earlier this week, I couldn't help but point out the similarity. |
Beta Was this translation helpful? Give feedback.
-
Of course, I'm not asking for it. Let developer explicitly declare what he knows for sure. Let me repeat. It is not so different from runtime IL emit approach to invoke generic methods from non-generic or less constrained code. IL emit approach is extremely faster than reflection, so CLR already can handle this "generic invoke issue" without additional effort. Just let make it static instead of runtime. If developer can declare use cases statically (in "switch on type argument" statement), so there is no need to emit IL opcodes at runtime. It should be done by C# compiler. |
Beta Was this translation helpful? Give feedback.
-
One another major performance consideration about ability to specialize generic code. Let's see void Invoke(ISomeInterface arg) // invoke by interface
{
// ...
}
void Invoke<T>(T arg) where T : ISomeInterface // invoke generic with constraint
{
// ...
} Second invoke (generic parameter with interface constraint) is much faster than first invoke (interface-typed parameter), especially when interface is implemented by struct. JIT and .NET Native static compiler are completely ignore interface type and use direct class/structure method invokes instead of virtual invokes on interface methods because it's well known that generic type have specific and exact methods, so invokes are optimized. This optimization especially important for structs as invokes on interface method require boxing, but invokes on interface-constrained struct generic type does not require boxing at all! At RavenDB repository there is special "Fast dictionary" class which exploit this constrained generics behavior. Article about constrained generics performance Citation:
You can figure how important language improvements of generics for high-performance apps and libs. |
Beta Was this translation helpful? Give feedback.
-
Shapes could allow some kind of "specialization" , e.g. shape Operation { void Do(); }
implement<T> Operation for T where T : struct { public void Do() { ... } }
implement<T> Operation for T where T : class { public void Do() { ... } } You could simulate that via extension methods but it causes ambiguity error (addressed by #98) static class X { public static void Do<T>(this T self) where T : class { ... } }
static class Y { public static void Do<T>(this T self) where T : struct { ... } } |
Beta Was this translation helpful? Give feedback.
-
@alrz, that approach doesn't work inside a generic method. (Which method would a uncontrained generic open type call?) The current technique that the BCL uses is a bit hokey for specialization. internal class Doer<T>
{
public virtual void DoIt(T member) => Console.WriteLine($"Normal route for {member}");
public static Doer<T> Instance { get; } = Specialize();
public static Doer<T> Specialize() => typeof(T).IsValueType ? Activator.CreateInstance(typeof(StructDoer<>).MakeGenericType(typeof(T))) as Doer<T> : new Doer<T>();
}
internal class StructDoer<T> : Doer<T> where T : struct
{
public override void DoIt(T member) => Console.WriteLine($"Struct route for {member}");
}
/*...*/
void MyGenericMethod<T>() => Doer<int>.Instance.DoIt(5); The CoreClr team uses it for things like specializing Updated it for a version that actually compiles. I didn't add the next level which is for nullable structs, so they fall into the non-specialized route. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Background
Implementation of generic methods sometimes require type-specific logic. For example, if generic argument T is
struct
it may significantly impact implementation details. For example, generic algorithms would be improved by passing large struct around by read-only reference instead of copy. But generic methods implementation don't know if actual type is struct (if no additional constraints specified) and reflection is often required to call type-specific constrained implementation.Example:
Proposed solution
Type-aware
switch
andis
.Beta Was this translation helpful? Give feedback.
All reactions