C#中操作的唯一性

huangapple go评论61阅读模式
英文:

Uniqueness of an Action in C#

问题

我需要在ConcurrentDictionary中存储Action<T>,并且我正在思考以下问题:

什么标识了一个动作的唯一性,以及如何将其存储在字典中,以确保字典中没有重复项?

在我的情况下,唯一性意味着如果两个类的实例将该动作添加到字典中,它们是唯一的。

静态方法只能添加一次。

我考虑了一些用于识别唯一性的想法(即“你尝试了什么?”的答案):

想法1:

我的第一个尝试是直接存储动作,但编译器告诉我不允许这样做,因为泛型Action<T>与字典的定义ConcurrentDictionary<Action<ISomething>, string>之间存在不匹配。

所以我尝试颠倒键和值,但是要用什么作为唯一键呢?

想法2:

使用action.GetHashCode()可能会导致冲突。

想法3:

如果我选择action.Method.DeclaringType加上action.Method.Name,两者将具有相同的键。

如果我选择action.Target.GetType().FullName + action.Method.Name,这将不起作用,因为动作可以是静态的,而action.Target将为空。

以下是代码示例,请随意将此可执行示例复制并粘贴到Visual Studio 2022的.NET6 ConsoleApplication模板的Program.cs文件中,查找我的问题,请参考Container.Add方法。

using System.Collections.Concurrent;
using System.Diagnostics.Metrics;

namespace UniquenessOfActionsInCSharp
{

    public interface IContainer
    {
        void Add<T>(Action<T> action) where T : ISomething;
        void Remove<T>(Action<T> action) where T : ISomething;
        void Share<T>(T something) where T : ISomething;
    }

    // 给出一个持有动作字典的容器类
    public class Container : IContainer
    {

        // 保护的并发字典
        protected readonly ConcurrentDictionary<Type, ConcurrentDictionary<string, Action<ISomething>>> InternalDict = new();
        protected readonly ConcurrentQueue<ISomething> InternalQueue = new ConcurrentQueue<ISomething>();

        // 返回已添加元素的数量
        public int Count<T>() => InternalDict.TryGetValue(typeof(T), out var innerDict) ? innerDict.Count : 0;

        // 添加元素,如果尚未添加
        // 是的,你需要保留方法签名不变
        public void Add<T>(Action<T> action) where T : ISomething
        {
            // 检查动作的唯一性,并仅在尚未添加时添加到InternalDict
            // TODO: 如何实现这个方法
        }

        public void Remove<T>(Action<T> action) where T : ISomething {}

        
        public void Share<T>(T something) where T : ISomething
        {
            // 将某事添加到队列中
            // 启动用于调用已添加到给定类型的动作的后台作业
        }

        // 遍历所有已添加的元素
        protected void BackgroundJob()
        {
            while (InternalQueue.TryDequeue(out ISomething something))
            {
                if (InternalDict.TryGetValue(something.GetType(), out var innerDict))
                {
                    foreach (var kvp in innerDict)
                    {
                        kvp.Value(something);
                    }
                }
            }
        }

    }

    // 存在多个ISomething的实现
    public interface ISomething
    {
        string Foo { get; set; }
    }

    // 为了简单起见,我只添加了SomethingA
    public class SomethingA : ISomething
    {
        public string Foo { get; set; } = "Bar";
        // 一些其他属性(与每个实现都不同)
    }

    public class SomethingB : ISomething
    {
        public string Foo { get; set; } = "Foo";
    }

    // 提供应添加到字典的动作的一些类
    public class Registrant
    {

        public static int StaticCount { get; private set; }

        public int CountA { get; private set; }
        public int CountB { get; private set; }

        public static void TheStaticAction(SomethingA a) { StaticCount++; }

        public void TheActionA(SomethingA a) { CountA++; }
        public void TheActionB(SomethingB b) { CountB++; }
    }

    // 用于那些对没有可执行代码示例表示疑虑的人的可执行代码示例
    public class Program
    {

        // 使用案例
        static void Main(string[] args)
        {

            // 创建设置
            Container  container = new Container();
            Registrant instance1 = new Registrant();
            Registrant instance2 = new Registrant();
            Registrant instance3 = new Registrant();

            // 进行添加调用并检查状态

                // 添加 1: 有效
        container.Add<SomethingA>(instance1.TheActionA);
        Console.WriteLine($"valid: {container.Count<SomethingA>() == 1} > instance1.TheActionA<SomethingA>(...) added");

        // 添加 2: 无效(动作已注册)
        container.Add<SomethingA>(instance1.TheActionA);
        Console.WriteLine($"valid: {container.Count<SomethingA>() == 1} > instance1.TheActionA<SomethingA>(...) skipped");

        // 添加 3: 有效(同一类的相同方法,但不同类的实例)
        container.Add<SomethingA>(instance2.TheActionA);
        Console.WriteLine($"valid: {container.Count<SomethingA>() == 2} > instance1.TheActionA<SomethingA>(...) added");

        // 添加 4: 无效(动作已注册)
        container.Add<SomethingA>(instance2.TheActionA);
        Console.WriteLine($"valid: {container.Count<SomethingA>() == 2} > instance1.TheActionA<SomethingA>(...) skipped");

        // 添加 5: 有效
        container.Add<SomethingA>(Registrant.TheStaticAction);
        Console.WriteLine($"valid: {container.Count<SomethingA>() == 3} > Registrant.TheStaticAction<SomethingA>(...) added");

        // 添加 6: 无效(静态方法不能添加两次)
        container.Add<SomethingA>(Registrant.TheStaticAction);
        Console.WriteLine($"valid: {

<details>
<summary>英文:</summary>

I need to store `Action&lt;T&gt;` in a `ConcurrentDictionary` and I am wrangling my head around the question: 

***What identifies an action as unique and how to store it in the dictionary so the dictionary ends up without duplicates?***

In my scenario uniqueness means if two instances of a class add the action to the dictionary they are unique.

A static method can be added only once.

Thoughts I had to identify the uniqueness (aka answer for &quot;what have you tried so far?&quot;)

**Thought 1:**

My first approach has been to store the action directly but the compiler told me it isn&#39;t allowed due to the mismatch between generics `Action&lt;T&gt;` and the definition of the dictionary `ConcurrentDictionary&lt;Action&lt;ISomething&gt;, string&gt;`.

So I tried to flip key and value but what to take as key unique key then?

**Thought 2**

Using `action.GetHashCode()` may result in conflicts.

**Thought 3**

If I go with `action.Method.DeclaringType` plus `action.Method.Name` both would have the same key.

If I go with `action.Target.GetType().FullName` + `action.Method.Name` it won&#39;t work because the action can be static and action.Taget will be null.


----------
**Provide some code:**
Please feel free to copy paste this executable sample into a .NET6 ConsoleApplication template `Program.cs` file within Visual Studio 2022.

See the method `Container.Add` to find my problem.

    
    using System.Collections.Concurrent;
    using System.Diagnostics.Metrics;
    
    namespace UniquenessOfActionsInCSharp
    {
    
        public interface IContainer
        {
            void Add&lt;T&gt;(Action&lt;T&gt; action) where T : ISomething;
            void Remove&lt;T&gt;(Action&lt;T&gt; action) where T : ISomething;
            void Share&lt;T&gt;(T something) where T : ISomething;
        }
    
        /// &lt;summary&gt;
        /// Given is a container class holding a dictionary of actions.
        /// &lt;/summary&gt;
        public class Container : IContainer
        {
    
            //protected readonly ConcurrentDictionary&lt;Action&lt;ISomething&gt;, string&gt; InternalDict = new();
            protected readonly ConcurrentDictionary&lt;Type, ConcurrentDictionary&lt;string, Action&lt;ISomething&gt;&gt;&gt; InternalDict = new();
            protected readonly ConcurrentQueue&lt;ISomething&gt; InternalQueue = new ConcurrentQueue&lt;ISomething&gt;();
    
            // returns the amount of added elements
            public int Count&lt;T&gt;() =&gt; InternalDict.TryGetValue(typeof(T), out var innerDict) ? innerDict.Count : 0;
    
            // adds an element if it is not already added
            // and yes you need to leave the signature as it is
            public void Add&lt;T&gt;(Action&lt;T&gt; action) where T : ISomething
            {
                // check uniqueness of an action and only add to the InternalDict if it is not already added
                // TODO: the question is how to implement this method
                
                //InternalSet.Add((x) =&gt; action((T)x));
            }
    
            public void Remove&lt;T&gt;(Action&lt;T&gt; action) where T : ISomething {}
    
            
            public void Share&lt;T&gt;(T something) where T : ISomething
            {
                // add something to a queue
                // start BackgroundJob for invoking actions added to the given type
            }
    
            // iterates over all added elements
            protected void BackgroundJob()
            {
                while (InternalQueue.TryDequeue(out ISomething something))
                {
                    if (InternalDict.TryGetValue(something.GetType(), out var innerDict))
                    {
                        foreach (var kvp in innerDict)
                        {
                            kvp.Value(something);
                        }
                    }
                }
            }
    
        }
    
        // there are multiple implementations of ISomething
        public interface ISomething
        {
            string Foo { get; set; }
        }
    
        // but for the sake of simplicity I just added SomethingA
        public class SomethingA : ISomething
        {
            public string Foo { get; set; } = &quot;Bar&quot;;
            // some other properties (different to each implementation)
        }
    
        public class SomethingB : ISomething
        {
            public string Foo { get; set; } = &quot;Foo&quot;;
        }
    
        // some class providing the actions that should be added to the dictionary
        public class Registrant
        {
    
            public static int StaticCount { get; private set; }
    
            public int CountA { get; private set; }
            public int CountB { get; private set; }
    
            public static void TheStaticAction(SomethingA a) { StaticCount++; }
    
            public void TheActionA(SomethingA a) { CountA++; }
            public void TheActionB(SomethingB b) { CountB++; }
        }
    
        // an executable code sample for those who mutters if it isn&#39;t there
        public class Program
        {
    
            // the use case
            static void Main(string[] args)
            {
    
                // create the setup
                Container  container = new Container();
                Registrant instance1 = new Registrant();
                Registrant instance2 = new Registrant();
                Registrant instance3 = new Registrant();
    
                // do the add calls and check state
    
                    // add 1: valid
            container.Add&lt;SomethingA&gt;(instance1.TheActionA);
            Console.WriteLine($&quot;valid: {container.Count&lt;SomethingA&gt;() == 1} &gt; instance1.TheActionA&lt;SomethingA&gt;(...) added&quot;);

            // add 2: invalid (the action is already registered)
            container.Add&lt;SomethingA&gt;(instance1.TheActionA);
            Console.WriteLine($&quot;valid: {container.Count&lt;SomethingA&gt;() == 1} &gt; instance1.TheActionA&lt;SomethingA&gt;(...) skipped&quot;);

            // add 3: valid (same method of a class but different instance of the class)
            container.Add&lt;SomethingA&gt;(instance2.TheActionA);
            Console.WriteLine($&quot;valid: {container.Count&lt;SomethingA&gt;() == 2} &gt; instance1.TheActionA&lt;SomethingA&gt;(...) added&quot;);

            // add 4: invalid (the action is already registered)
            container.Add&lt;SomethingA&gt;(instance2.TheActionA);
            Console.WriteLine($&quot;valid: {container.Count&lt;SomethingA&gt;() == 2} &gt; instance1.TheActionA&lt;SomethingA&gt;(...) skipped&quot;);

            // add 5: valid
            container.Add&lt;SomethingA&gt;(Registrant.TheStaticAction);
            Console.WriteLine($&quot;valid: {container.Count&lt;SomethingA&gt;() == 3} &gt; Registrant.TheStaticAction&lt;SomethingA&gt;(...) added&quot;);

            // add 6: invalid (static methods can&#39;t be added twice)
            container.Add&lt;SomethingA&gt;(Registrant.TheStaticAction);
            Console.WriteLine($&quot;valid: {container.Count&lt;SomethingA&gt;() == 3} &gt; Registrant.TheStaticAction&lt;SomethingA&gt;(...) skipped&quot;);

            // add 7: valid (same method of a class but different instance of the class)
            container.Add&lt;SomethingB&gt;(instance3.TheActionB);
            Console.WriteLine($&quot;valid: {container.Count&lt;SomethingB&gt;() == 1} &gt; instance1.TheAction&lt;SomethingB&gt;(...) added&quot;);

            // add 8: invalid (the action is already registered)
            container.Add&lt;SomethingB&gt;(instance3.TheActionB);
            Console.WriteLine($&quot;valid: {container.Count&lt;SomethingB&gt;() == 1} &gt; instance1.TheAction&lt;SomethingB&gt;(...) skipped&quot;);


            // invoking
            container.Share(new SomethingB());
            container.Share(new SomethingA());

            Thread.Sleep(5000);

            // and cross checking (all actions called only once though tried to add them twice)
            Console.WriteLine($&quot;valid: {instance1.CountA == 1 &amp;&amp; instance1.CountB == 0} &gt; instance1.CountA == {instance1.CountA} &amp;&amp; instance1.CountB == {instance1.CountB}&quot;);
            Console.WriteLine($&quot;valid: {instance2.CountA == 1 &amp;&amp; instance2.CountB == 0} &gt; instance2.CountA == {instance2.CountA} &amp;&amp; instance2.CountB == {instance2.CountB}&quot;);
            Console.WriteLine($&quot;valid: {Registrant.StaticCount == 1} &gt; Registrant.StaticCount == {Registrant.StaticCount}&quot;);
            Console.WriteLine($&quot;valid: {instance3.CountA == 0 &amp;&amp; instance3.CountB == 1} &gt; instance3.CountA = {instance3.CountA} &amp;&amp; instance3.CountB == {instance3.CountB}&quot;);

    
            }
    
        }
    
    }

If the console output writes &quot;valid: true &gt;&quot; in each line my question is answered.

----------

The hashset approach

[![enter image description here][4]][4]

I can add with

     InternalSet.Add((x) =&gt; action((T)x));

but losing all chance for checking uniqueness. So I decided for a CONCURRENT dictionary where I need some key.

----

I don&#39;t care which collection is used.

I don&#39;t care how concurrency is handled as long it is handled.

It is not allowed to change the interface removing the generic.

by the way I already have an working solution in my code using a dictionary but I am asking to may find better solutions because I am not satisfied with my current code.

---

  [4]: https://i.stack.imgur.com/oUvLf.png

</details>


# 答案1
**得分**: 2

看起来对我来说,你不能存储一个 `Action&lt;ISomething&gt;`,因为你不知道 `Action` 将会以什么样的确切类型作为参数。你只能将其向上转型为一个 *更具体*  `ISomething`,而不能将其向下转型为一个接口。

所以我们可以将其声明为 `Delegate`(它内建了相等性函数),我们可以将其转型为 `T`,因为我们知道我们将会从字典中使用 `typeof(T)` 获取到正确的类型。

为了调用这个方法,我们可以使用反射。但一个更好的选择是,与每个委托一起存储一个 lambda 表达式,它知道如何将 `ISomething` 转型为 `T`。因此,每一个都被存储在内部字典中,成对地是 `Delegate, Action&lt;ISomething&gt;`,而 lambda 表达式简单地被创建为 `obj =&gt; action((T)obj)`。 

```csharp
public class Container : IContainer
{
    // ...
}

请注意,something.GetType() 如果存在多级继承树可能不会从你的字典中返回结果。所以你可能需要递归地沿着继承链往下走。

protected void BackgroundJob()
{
    while (InternalQueue.TryDequeue(out ISomething something))
    {
        for (var type = something.GetType(); type != typeof(object); type = type.BaseType)
        {
            if (InternalDict.TryGetValue(something.GetType(), out var innerDict))
            {
                foreach (var kvp in innerDict)
                {
                    kvp.Value(something);
                }
                break;  // 继续 while 循环
            }
        }
    }
}
英文:

It looks to me that you cannot store a Action&lt;ISomething&gt;, because you don't know what exact type the Action will take as a parameter. You can only upcast it to a more derived ISomething, not downcast it to an interface.

So instead we can just declare it as Delegate (which has equality functions built in), and we can cast it to T because we know we will get the right one from the dictionary using typeof(T).

In order to call this, we could use reflection. But a much better option is to instead store with each delegate, a lambda that knows how to cast an ISomething to T. So each one gets stored in the inner dictionary as a pair of Delegate, Action&lt;ISomething&gt;, and the lambda is created simply as obj =&gt; action((T)obj).

public class Container : IContainer
{
protected readonly ConcurrentDictionary&lt;Type, ConcurrentDictionary&lt;Delegate, Action&lt;ISomething&gt;&gt;&gt; InternalDict = new();
protected readonly ConcurrentQueue&lt;ISomething&gt; InternalQueue = new ConcurrentQueue&lt;ISomething&gt;();
// returns the amount of added elements
public int Count&lt;T&gt;() =&gt; InternalDict.TryGetValue(typeof(T), out var innerDict) ? innerDict.Count : 0;
// adds an element if it is not already added
public void Add&lt;T&gt;(Action&lt;T&gt; action) where T : ISomething
{
InternalDict.AddOrUpdate(typeof(T),
(key, arg) =&gt; new ConcurrentDictionary&lt;Delegate, Action&lt;ISomething&gt;&gt;(new[] { arg }),
(key, innerDict, arg) =&gt; {
innerDict.TryAdd(arg.Key, arg.Value);
return innerDict;
},
new KeyValuePair&lt;Delegate, Action&lt;ISomething&gt;&gt;(action, obj =&gt; action((T)obj))
);
}
public void Remove&lt;T&gt;(Action&lt;T&gt; action) where T : ISomething
{
if (InternalDict.TryGetValue(typeof(T), out var innerDict))
{
innerDict.TryRemove(action, out var actor);
}
}
public void Share&lt;T&gt;(T something) where T : ISomething
{
InternalQueue.Enqueue(something);
// start BackgroundJob for invoking actions added to the given type
// unclear what should go here, maybe a Task.Run??
}
// iterates over all added elements
protected void BackgroundJob()
{
while (InternalQueue.TryDequeue(out ISomething something))
{
if (InternalDict.TryGetValue(something.GetType(), out var innerDict))
{
foreach (var kvp in innerDict)
{
kvp.Value(something);
}
}
}
}
}

Note that something.GetType() may not return a result from your dictionary if there is a multi-level inheritance tree. So you may need to recursively go down the inheritance stack

    protected void BackgroundJob()
{
while (InternalQueue.TryDequeue(out ISomething something))
{
for (var type = something.GetType(); type != typeof(object); type = type.BaseType)
{
if (InternalDict.TryGetValue(something.GetType(), out var innerDict))
{
foreach (var kvp in innerDict)
{
kvp.Value(something);
}
break;  // continue the while loop
}
}
}
}

答案2

得分: 1

Action&lt;T&gt; 委托 已经实现了一个符合您要求的 Equals() 方法:

> 方法和目标将按以下方式进行比较:
>
> * 如果比较的两个方法都是静态的,并且是同一个类上的相同方法,则认为这两个方法是相等的,目标也被认为是相等的。
>
> * 如果比较的两个方法是实例方法,并且是同一个对象上的相同方法,则认为这两个方法是相等的,目标也被认为是相等的。
>
> * 否则,将不认为这两个方法是相等的,目标也不被认为是相等的。

这意味着在您的示例中,动作 instance1.TheActioninstance2.TheAction 动作不相同,因为它们引用不同的目标(instance1instance2 引用不同的实例)。并且这些动作与 Registrant.TheStaticAction 动作也不相等,因为它们既不引用相同的方法TheActionTheStaticAction),也不引用相同的目标instance1null)。

这使得您可以简单地使用 ISet&lt;Delegate&gt;,它不允许添加与集合中已有元素相等的第二个实例。如果将 InternalDict 字段的类型更改为 ISet,则可以满足您的要求。参见以下代码:

// ...

(为了测试目的,我将 BackgroundJob() 方法设为了公共的)

BackgroundJob() 方法稍微复杂一些,因为您必须搜索正确的 Action&lt;T&gt;/Delegate 来使用。这个实现将查看方法的第一个参数的参数类型,并将其与下一个队列项的类型进行比较。如果匹配,它将执行该动作/委托。

当您运行此代码时,将会得到以下输出:

valid: True > instance1.TheActionA<SomethingA>(...) skipped
valid: True > instance1.TheActionA<SomethingA>(...) added
valid: True > instance1.TheActionA<SomethingA>(...) skipped
valid: True > Registrant.TheStaticAction<SomethingA>(...) added
valid: True > Registrant.TheStaticAction<SomethingA>(...) skipped
valid: True > instance1.TheAction<SomethingB>(...) added
valid: True > instance1.TheAction<SomethingB>(...) skipped
valid: True > instance1.CountA == 1 && instance1.CountB == 0
valid: True > instance2.CountA == 1 && instance2.CountB == 0
valid: True > Registrant.StaticCount == 1
valid: True > instance3.CountA == 0 && instance3.CountB == 1

正如您所见,所有的断言都是 true

您可以根据特定要求在每次访问周围添加 lock 块以处理并发。一个简单的锁定每个访问周围的块将起作用,这将满足您的要求:

> 只要处理了并发,我就不在乎是如何处理的。

英文:

The Action&lt;T&gt; delegate already implements an Equals() method which fits your requirement:

> The methods and targets are compared for equality as follows:
>
> * If the two methods being compared are both static and are the same method on the same class, the methods are considered equal and the targets are also considered equal.
>
> * If the two methods being compared are instance methods and are the same method on the same object, the methods are considered equal and the targets are also considered equal.
>
> * Otherwise, the methods are not considered to be equal and the targets are also not considered to be equal.

This means that in your example, the action instance1.TheAction is not the same as the instance2.TheAction action, because they reference different targets (instance1 and instance2 reference different instances). And these actions are not equal to the Registrant.TheStaticAction action, because they don't reference the same method (TheAction vs. TheStaticAction) as well as not reference the same target (instance1 vs. null).

This allows you to simply use a ISet&lt;Delegate&gt;, which doesn't allow adding a second instance which is equal with an element already in the set. If you change the type of the InternalDict field to a ISet you will get your desired requirements. See the following code:

public interface IContainer
{
void Add&lt;T&gt;(Action&lt;T&gt; action) where T : ISomething;
void Remove&lt;T&gt;(Action&lt;T&gt; action) where T : ISomething;
void Share&lt;T&gt;(T something) where T : ISomething;
}
/// &lt;summary&gt;
/// Given is a container class holding a dictionary of actions.
/// &lt;/summary&gt;
public class Container : IContainer
{
protected readonly ISet&lt;Delegate&gt; actions = new HashSet&lt;Delegate&gt;();
protected readonly ConcurrentQueue&lt;ISomething&gt; InternalQueue = new ConcurrentQueue&lt;ISomething&gt;();
// returns the amount of added elements
public int Count&lt;T&gt;()
{
return actions.OfType&lt;Action&lt;T&gt;&gt;().Count();
}
// adds an element if it is not already added
// and yes you need to leave the signature as it is
public void Add&lt;T&gt;(Action&lt;T&gt; action) where T : ISomething
{
actions.Add(action);
}
public void Remove&lt;T&gt;(Action&lt;T&gt; action) where T : ISomething
{
actions.Remove(action);
}
public void Share&lt;T&gt;(T something) where T : ISomething
{
// add something to a queue
// start BackgroundJob for invoking actions added to the given type
InternalQueue.Enqueue(something);
}
// iterates over all added elements
public void BackgroundJob()
{
while (InternalQueue.TryDequeue(out ISomething something))
{
//Console.WriteLine(&quot;Got a: &quot;+something);
foreach (Delegate entry in this.actions)
{
ParameterInfo[] parameters = entry.Method.GetParameters();
ParameterInfo parameter = parameters[0];
Type parameterType = parameter.ParameterType;
if (parameterType == something.GetType())
{
//Console.WriteLine(&quot;Match, call it&quot;);
entry.DynamicInvoke(something);
}
}
}
}
}
// there are multiple implementations of ISomething
public interface ISomething
{
string Foo { get; set; }
}
// but for the sake of simplicity I just added SomethingA
public class SomethingA : ISomething
{
public string Foo { get; set; } = &quot;Bar&quot;;
// some other properties (different to each implementation)
}
public class SomethingB : ISomething
{
public string Foo { get; set; } = &quot;Foo&quot;;
}
// some class providing the actions that should be added to the dictionary
public class Registrant
{
public static int StaticCount { get; private set; }
public int CountA { get; private set; }
public int CountB { get; private set; }
public static void TheStaticAction(SomethingA a) { StaticCount++; }
public void TheActionA(SomethingA a) { CountA++; }
public void TheActionB(SomethingB b) { CountB++; }
}
// an executable code sample for those who mutters if it isn&#39;t there
public class Program
{
// the use case
static void Main(string[] args)
{
// create the setup
Container  container = new Container();
Registrant instance1 = new Registrant();
Registrant instance2 = new Registrant();
Registrant instance3 = new Registrant();
// do the add calls and check state
// add 1: valid
container.Add&lt;SomethingA&gt;(instance1.TheActionA);
Console.WriteLine($&quot;valid: {container.Count&lt;SomethingA&gt;() == 1} &gt; instance1.TheActionA&lt;SomethingA&gt;(...) added&quot;);
// add 2: invalid (the action is already registered)
container.Add&lt;SomethingA&gt;(instance1.TheActionA);
Console.WriteLine($&quot;valid: {container.Count&lt;SomethingA&gt;() == 1} &gt; instance1.TheActionA&lt;SomethingA&gt;(...) skipped&quot;);
// add 3: valid (same method of a class but different instance of the class)
container.Add&lt;SomethingA&gt;(instance2.TheActionA);
Console.WriteLine($&quot;valid: {container.Count&lt;SomethingA&gt;() == 2} &gt; instance1.TheActionA&lt;SomethingA&gt;(...) added&quot;);
// add 4: invalid (the action is already registered)
container.Add&lt;SomethingA&gt;(instance2.TheActionA);
Console.WriteLine($&quot;valid: {container.Count&lt;SomethingA&gt;() == 2} &gt; instance1.TheActionA&lt;SomethingA&gt;(...) skipped&quot;);
// add 5: valid
container.Add&lt;SomethingA&gt;(Registrant.TheStaticAction);
Console.WriteLine($&quot;valid: {container.Count&lt;SomethingA&gt;() == 3} &gt; Registrant.TheStaticAction&lt;SomethingA&gt;(...) added&quot;);
// add 6: invalid (static methods can&#39;t be added twice)
container.Add&lt;SomethingA&gt;(Registrant.TheStaticAction);
Console.WriteLine($&quot;valid: {container.Count&lt;SomethingA&gt;() == 3} &gt; Registrant.TheStaticAction&lt;SomethingA&gt;(...) skipped&quot;);
// add 7: valid (same method of a class but different instance of the class)
container.Add&lt;SomethingB&gt;(instance3.TheActionB);
Console.WriteLine($&quot;valid: {container.Count&lt;SomethingB&gt;() == 1} &gt; instance1.TheAction&lt;SomethingB&gt;(...) added&quot;);
// add 8: invalid (the action is already registered)
container.Add&lt;SomethingB&gt;(instance3.TheActionB);
Console.WriteLine($&quot;valid: {container.Count&lt;SomethingB&gt;() == 1} &gt; instance1.TheAction&lt;SomethingB&gt;(...) skipped&quot;);
// invoking
container.Share(new SomethingB());
container.Share(new SomethingA());
container.BackgroundJob();
// and cross checking (all actions called only once though tried to add them twice)
Console.WriteLine($&quot;valid: {instance1.CountA == 1 &amp;&amp; instance1.CountB == 0} &gt; instance1.CountA == 1 &amp;&amp; instance1.CountB == 0&quot;);
Console.WriteLine($&quot;valid: {instance2.CountA == 1 &amp;&amp; instance2.CountB == 0} &gt; instance2.CountA == 1 &amp;&amp; instance2.CountB == 0&quot;);
Console.WriteLine($&quot;valid: {Registrant.StaticCount == 1} &gt; Registrant.StaticCount == 1&quot;);
Console.WriteLine($&quot;valid: {instance3.CountA == 0 &amp;&amp; instance3.CountB == 1} &gt; instance3.CountA == 0 &amp;&amp; instance3.CountB == 1&quot;);
}
}

(For testing purposes, I have made the BackgroundJob() method public)

The BackgroundJob() method is a little bit more complicated since you have to search for the right Action&lt;T&gt;/Delegate to use. This implementation will look at the argument type of the first parameter of the method and compare it against the type of the next queue item. If it is a match, it will execute the action/delegate.

When you run this code you will get the following output:

valid: True &gt; instance1.TheActionA&lt;SomethingA&gt;(...) skipped
valid: True &gt; instance1.TheActionA&lt;SomethingA&gt;(...) added
valid: True &gt; instance1.TheActionA&lt;SomethingA&gt;(...) skipped
valid: True &gt; Registrant.TheStaticAction&lt;SomethingA&gt;(...) added
valid: True &gt; Registrant.TheStaticAction&lt;SomethingA&gt;(...) skipped
valid: True &gt; instance1.TheAction&lt;SomethingB&gt;(...) added
valid: True &gt; instance1.TheAction&lt;SomethingB&gt;(...) skipped
valid: True &gt; instance1.CountA == 1 &amp;&amp; instance1.CountB == 0
valid: True &gt; instance2.CountA == 1 &amp;&amp; instance2.CountB == 0
valid: True &gt; Registrant.StaticCount == 1
valid: True &gt; instance3.CountA == 0 &amp;&amp; instance3.CountB == 1

As you see, all your asserts are true.

You can add lock blocks to handle concurrencies depending on your specific requirements. A simple lock around each access would work, which will fulfill your requirement:

> I don't care how concurrency is handled as long it is handled.

huangapple
  • 本文由 发表于 2023年7月7日 05:09:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/76632542.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定