-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Confusing error when generic extension methods with in this
and T struct not compiling.
#24601
Comments
This is bydesign. At least now. Anything that you do with ‘this’ here will have to be an interface call and thus would result in a copy. The pattern would be nearly always a bad idea - worse than ordinary byval extension. This is not a first bug report though. I wonder if we should change the error message or just allow this. |
Also curious if the error prevents some scenario, or just unintuitive. Why is the expectation that this should compile? |
in this
and T struct not compilingref this
and T struct not compiling
Sorry, I have changed |
💭 I just realized that semantically this doesn't make sense unless the parameter is |
It should compile because a struct constraint will resolve to a reification of the method by the runtime, so no interface calls will be in place. |
But this works in 7.2? public static class TestExtension
{
public static void TestDispose<T>(ref this T thisArg) where T : struct, IDisposable
{
}
} For Also accessing fields would be ok, so maybe warning/error on calling a method/property rather than error for using |
Hold on, it works indeed... [Edit]Ok, resharper was not giving an error, while Roslyn was just compiling fine, that's why I end up here...[/Edit] |
"Warning calling a method on a readonly non-readonly struct will cause a copy; and the method will be called on the copy" - but with better language. |
ref this
and T struct not compilingin this
and T struct not compiling
Changing back the issue to |
That is the conundrum: This makes sense and allowed. public static class TestExtension
{
public static void TestDispose<T>(ref this T thisArg) where T : struct, IDisposable
{
}
} The following is a "pit of despair" code. It is just subtly different from the code above, but at best it leads to suboptimal performance due to unexpected and not apparent copying, and at worst to bugs - since interface invocations are currently always done on a copy. public static class TestExtension
{
public static void TestDispose<T>(in this T thisArg) where T : struct, IDisposable
{
}
} Compiler disallows the second variant and that still feels right. However, I have a mental counter on how many time this triggered a bug report and it is at the state "we need to do something". Long term we can introduce Something like: |
in this
and T struct not compilingin this
and T struct not compiling, error is confusing
in this
and T struct not compiling, error is confusingin this
and T struct not compiling.
Maybe one day we will have methods tag-able with "readonly" and we will be able to introduce proper |
@VSadov Does this issue only affect generic methods? It seems like a non-generic method should allow |
Its only generic methods; non-generic methods allow both readonly structs and non-readonly structs. It doesn't make sense for generics and non-readonly structs as the only methods you can use are ones constrained by the interface and interfaces don't have field access only properties so every use of a non-reaodnly struct will cause a copy, removing the purpose of
|
@VSadov Your comments seem to contradict this. If the struct constraint doesn't resolve to the reification of the method thereby removing the interface call and associated copy, why doesn't it? |
@halter73 - the problem is not in the interface dispatch. Compiler must assume that interface members will mutate receivers. We do pass the receiver by reference to the call, so they certainly can. And such copy would have to happen every time any interface member is used on |
That makes sense. Thanks for the explanation. |
Why is this not allowed? ... public static void ExtensionMethod<T>(in this T thisArg) where T : struct, IComparable<T>
{ ... } ... and then it is allowed when you use ref, instead? (about defensive copies: 1710) |
struct S : I { }
interface I { }
static class Ext
{
static void A<T>(this in T s) where T : struct, I { } // error
static void B<T>(this ref T s) where T : struct, I { } // ok
static void C<T>(in T s) where T : struct, I { } // ok
static void D(this in S s) { } // ok
} I list up the cases. |
Indeed allowing
Is this going to happen? The constraint approach effectively disentangles the new capability's truly beneficial uses from the murky or dubious For the purpose of better championing this feature proposal, I just wanted to point out that it feels more focused, and thus perhaps even more compelling, when presented and understood as independent of the red-herring |
Here's the part where I confess to subsequently realizing that maybe all of "truly beneficial uses" I envisioned a moment ago are not so mainstream... As currently implemented throughout my code, it turns out that all of the various C# method bodies I was thinking about for this in fact start with rude coercion of the managed pointer. Because what else can it actually do? Now to most people, the obvious necessity of doing so would be the only way to puzzle any sense out of an otherwise kooky claim. Like, "kooky" that I didn't mention such an important point. For me, apparently I've lived so long now deep within my land of particularly unorthodox C# where So anyway, having explained that embarrassment, and although the feature now appears to be far less earth-shattering than I imagined for pretty much everyone but me, suffice it to say that a |
I do not think an error nor a warning is justifiable in this case. Generic After all, you can do this without any issue: private delegate void TestInDelegate<T>(in T val);
private delegate void TestRefDelegate<T>(ref T val);
public static void Test1<T>(in T val) where T : struct
{
var del = (TestInDelegate<T>)Delegate.CreateDelegate(typeof(TestInDelegate<T>), ((TestRefDelegate<T>)Test2<T>).Method);
del.Invoke(in val);
}
public static void Test2<T>(ref T val) where T : struct
{
val = default;
} Calling So, there are 4 things that need to be done:
|
What @IllidanS4 said. Agree 100% and a million upvotes. |
To bad I'm facing the same problem in my code base. I have implemented an arbitrary precision floating point library. The JIT compiler really does an incredible good job with function inlining and generic code expansion. At the moment every 'in' argument passing is done by using 'ref'.
Unintended struct copy does not (and would not) occure during this usage. BR Gerhard
|
Note: as far as i can tell, the Roslyn compiler is abiding by the current language spec properly. If you'd like a change in behavior here, a proposal will need to be filed over at dotnet/csharplang. This issue only tracks improving the confusing diagnostic message that is currently being reported. |
Case: Using generic extension method with
in this T
and where T is a structVersion Used:
C# 7.2 (VS 2017 15.5.4)
Steps to Reproduce:
Expected Behavior:
The extension method should compile.
Actual Behavior:
The text was updated successfully, but these errors were encountered: