-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Fix resolving types with primary constructors using in parameters #105928
Fix resolving types with primary constructors using in parameters #105928
Conversation
@dotnet-policy-service agree |
Can you elaborate on why you want to inject a service with |
I would ask the inverse of that - is there any reason why Actual use-case here was that I wanted to both use a primary constructor for simplicity, and wanted injected services to be It is possible that this is not the most correct fix - anecdotally it does resolve the issue for my use-case though. Is there a more correct way to detect |
A value type A reference type A An
So consider the following using System;
using System.Diagnostics;
MyClass c = new MyClass();
c.X = 0;
M1(c);
Console.WriteLine(c.X); // prints 1
M2(c);
Console.WriteLine(c.X); // prints 2
MyStruct s = new MyStruct();
s.X = 0;
s.C = new MyClass();
s.C.X = 0;
M3(s);
Console.WriteLine(s.X); // prints 0
Console.WriteLine(s.C.X); // prints 1
M4(s);
Console.WriteLine(s.X); // prints 0
Console.WriteLine(s.C.X); // prints 2
void M1(in MyClass c)
{
// This is disallowed, CS8331
// c = new MyClass();
// This is allowed, because we're not mutating 'c', we're mutating the data 'c' refers to
c.X = 1;
}
void M2(MyClass c)
{
// This is allowed, because we're not mutating 'c', we're mutating the data 'c' refers to
c.X = 2;
// This is allowed
c = new MyClass();
// This is allowed, but won't be visible externally because we're no longer referring to the same data as the caller
c.X = 3;
}
void M3(in MyStruct s)
{
// This is disallowed, CS8331
// s = new MyStruct();
// This is disallowed, CS8332
// s.X = 1;
// This is disallowed, CS8332
// s.C = new MyClass();
// This is allowed, because we're not mutating 's', we're mutating the data 's.C' refers to
s.C.X = 1;
}
void M4(MyStruct s)
{
// This is allowed, but won't be visible because s is a copy of the data the caller has
s.X = 1;
// This is allowed, and will be visible because even though its a copy, the reference still points to the same data as the caller
s.C.X = 2;
// This is allowed
s = new MyStruct();
// s was overwritten with a new instance, so s.C is null now
Debug.Assert(s.C == null);
s.C = new MyClass();
// This is allowed and won't be visible, because it refers to a new location
s.C.X = 3;
}
public class MyClass
{
public int X { get; set; }
}
public struct MyStruct
{
public int X { get; set; }
public MyClass C { get; set; }
} So |
Thanks for the detailed explanation - I definitely learned a bit here. However, I believe you may be overlooking the behavior of class Dep() { }
class TestClass(in Dep a)
{
void SomeMethod()
{
// CS9109 and CS8331 both prevent this from compiling
a = new Dep();
}
} Without this pattern does require adding |
The behavior doesn't change, it is still taking the value byref and still blocking the same scenarios. Not using
|
Yes, but injecting and assigning as class Dep() { }
class TestClass(in Dep a)
{
private readonly Dep _a = a;
void SomeMethod()
{
// will be readonly
_a.Equals(_a);
// will not compile as it is not in scope
a.Equals(a);
}
} Implementing In either case, I think that my question from above still stands:
|
We should not be checking into main for V9. See #105924 (comment). |
As indicated above, this doesn't do what you think it does and doesn't add any actual protection, its just adding unnecessary cost to the callsite. The right thing to do is simply: class Dep() { }
class TestClass(Dep a)
{
private readonly Dep _a = a;
}
For proper usages of |
Is there a different branch this can target to be included in V10?
The example you provided does not solve my concern, and in fact makes user-errors more likely. While this does create a class Dependency { };
class Dependent(Dependency a)
{
private readonly Dependency _a = a;
public void UseWrongInstance()
{
a = new Dependency();
}
}; I would argue that this pattern actually makes human-errors more likely, as there will be 2 variables, typically with near-identical names, in scope for IntelliSense to recommend.
So from what I understand, you agree that the DependencyInjection library should support injection of |
Not quite. The sentiment is that some valid scenario may exist, in which case it may be valid to add that support. But that support should be considered in the context of a valid and real world usage scenario. Such a scenario needs to be explicitly detailed with the relevant rational, tests, etc. Adding features/support should not be considered or driven with regards to non-typical usage scenarios; such as using
This is no different than existing constructor parameters or private fields/public properties which have existed for the 20+ year history of C#/.NET. Auto properties were not always a thing, even with auto properties you often need logic on top and so it is extremely typical to have one, both, or all of
The "correct" solution here would be to write an analyzer that flags the usage you don't want to have happen in your codebase. The next best thing would be to log a proposal for a new feature on https://github.com/dotnet/csharplang detailing that you'd like a way to specify that primary constructor variables are only available for field/property assignment and are not available in derived contexts, as you'd like to enforce that users go through the field/property instead. |
Closing; see #105924 (comment) |
Fixes #105924
in
primary constructor parameters are treated as reference types when inspected. Fix here is simply to use the referenced type instead when resolving parameters.