I was trying to create a simple extension method wrapper around CollectionsMarshal.GetValueRefOrAddDefault(..) and I ran into a problem I don't quite understand.
This code works:
public static ref TValue? GetValueRefOrAddDefault<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, out bool exists)
where TKey : notnull
{
return ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out exists);
}
If I try not to pass the out bool exists variable outside of the method by discarding it or by just storing it in a local variable, it doesn't work:
public static ref TValue? GetValueRefOrAddDefault<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key)
where TKey : notnull
{
return ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out _);
// Error: Cannot use a result of CollectionsMarshal.GetValueRefOrAddDefault(..) in this context because
// it may expose variables referenced by parameter 'exists' outside of their declaration scope
}
To me this error sounds a lot like something I should get when trying to return a reference to a local variable. I just don't get why I'm getting the error by simply not using / passing out the boolean I received, and how could that omission possibly reference anything out of scope.
Also, the error disappears when I use a dummy variable outside of the scope of the method, but I'd rather not do that:
private static bool dummy;
public static ref TValue? GetValueRefOrAddDefault<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key)
where TKey : notnull
{
return ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out dummy);
}
As @RichardDeeming noted in comment: the returned reference could be a reference to the out parameter, which is a local variable in the discard case.
This issue is resolved by C#11 Low-level struct improvements # Change the behavior of out parameters
the language will change the default ref-safe-to-escape value for
outparameters to be current method. Effectivelyoutparameters are implicitlyscoped outgoing forward.
......
The language will also no longer consider arguments passed to anoutparameter to be returnable.
Note: if you using <LangVersion> 11 or higher with pre net7 TFM, compiler will use those new rules, but reference assembly would still be annotated as RefSafetyRules(10 /*or less*/) so out var defined in GetValueRefOrAddDefault would still not be treated as scoped.
You can define UnscopedRefAttribute and annotate your wrapper out var with it to resolve this issue.
namespace System.Diagnostics.CodeAnalysis
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public sealed class UnscopedRefAttribute : Attribute {}
}
public static ref TValue? GetValueRefOrAddDefault<TKey, TValue>(
this Dictionary<TKey, TValue> dictionary, TKey key,
[UnscopedRef] out bool exists)
{ ... }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With