Skip to content

[Analyzer] Incorrect lambda type passed to ConcurrentDictionary.GetOrAdd #102398

Open

Description

ConcurrentDictionary<>.GetOrAdd exposes two overloads, both of which accept an argument (the key). Yet, this code compiles:

using System.Collections.Concurrent;

public static class LockProvider
{
    private static readonly ConcurrentDictionary<string, object> s_locks = new();
    
    public static object GetUniqueLock(string key) => s_locks.GetOrAdd(key, () => new object());
}

SharpLab

Why? Because a) there's also a GetOrAdd(TKey key, TValue value) overload, b) thanks to the lambda improvements in C# 10 it'll will infer a delegate type that matches the inputs/outputs of the lambda (in this case, Func<TValue>), and c) Func<TValue> is a TValue here (Func<TValue> : object). Thus, this code compiles down to effectively:

using System.Collections.Concurrent;

public static class LockProvider
{
    private static readonly ConcurrentDictionary<string, object> s_locks = new();
    
    public static object GetUniqueLock(string key)
    {
        Func<object> value = Cache.CachedDelegate ??= () => object();
        return s_locks.GetOrAdd(key, value);
    }
}

internal static class Cache
{
    public static Func<object>? CachedDelegate;
}

and the delegate is never actually used as a delegate. Rather, barring initialization race conditions, the same object ends up being used as the value for every key and thus being returned as the same lock object in this example for every key.

This has shown up in several code bases now, such that we should consider writing an analyzer for it, either for the specific case with ConcurrentDictionary, or possibly broadened out to cases where a lambda is being passed as a argument for some object or unconstrained generic parameter (if that wouldn't be too noisy).

cc: @jaredpar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    api-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-System.Collectionscode-analyzerMarks an issue that suggests a Roslyn analyzercode-fixerMarks an issue that suggests a Roslyn code fixer

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions