比较 C# 中的委托

本文关键字:比较 | 更新日期: 2023-09-27 17:56:59

我需要比较 C# 委托的相等性。我认为两个委托在对象(或静态)的同一实例上调用相同的方法,或者如果它们的方法主体具有完全相同的编译 IL,则认为它们是相等的。下面的代码包括我需要比较才能通过的测试用例:

using System;
namespace ConsoleApplication
{
    public delegate int Compare<Type>(Type left, Type right);
    public delegate int Compare<Left, Right>(Left left, Right right);
    public class Program
    {
        public static void Main(string[] args)
        {
            // Test 0 (false control)
            Action _0_1 = () => { };
            Action<int> _0_2 = (int i) => { Math.Sign(i); };
            Console.WriteLine("0:'t" + (Equate(_0_1, _0_2) == false));
            // Test 1s (same type delegates from static-method)
            Compare<int> _1s_1 = Test;
            Compare<int> _1s_2 = Test;
            Console.WriteLine("1s:'t" + Equate(_1s_1, _1s_2));
            // Test 1i (same type delegates from instance-method)
            Program _1i_0 = new Program();
            Compare<int> _1i_1 = _1i_0.Test3;
            Compare<int> _1i_2 = _1i_0.Test3;
            Console.WriteLine("1i:'t" + Equate(_1i_1, _1i_2));
            // Test 2s (same type delegates from same type static-delegates)
            Compare<int> _2s_1 = new Compare<int>(_1s_1);
            Compare<int> _2s_2 = new Compare<int>(_1s_2);
            Console.WriteLine("2s:'t" + Equate(_2s_1, _2s_2));
            // Test 2i (same type delegates from same type instance-delegates)
            Compare<int> _2i_1 = new Compare<int>(_1i_1);
            Compare<int> _2i_2 = new Compare<int>(_1i_2);
            Console.WriteLine("2i:'t" + Equate(_2i_1, _2i_2));
            // Test 3s (different type delegates from static-method)
            Compare<int> _3s_1 = Test;
            Compare<int, int> _3s_2 = Test;
            Console.WriteLine("3s:'t" + Equate(_3s_1, _3s_2));
            // Test 3i (different type delegates from instance-method)
            Program _3i_0 = new Program();
            Compare<int> _3i_1 = _3i_0.Test3;
            Compare<int, int> _3i_2 = _3i_0.Test3;
            Console.WriteLine("3i:'t" + Equate(_3i_1, _3i_2));
            // Test 4s (same type delegates from different type static-delegates)
            Compare<int> _4s_1 = new Compare<int>(_3s_1);
            Compare<int> _4s_2 = new Compare<int>(_3s_2);
            Console.WriteLine("4s:'t" + Equate(_4s_1, _4s_2));
            // Test 4i (same type delegates from different type instance-delegates)
            Compare<int> _4i_1 = new Compare<int>(_3i_1);
            Compare<int> _4i_2 = new Compare<int>(_3i_2);
            Console.WriteLine("4i:'t" + Equate(_4i_1, _4i_2));
            // Test 4s.1 (same type delegates from different type static-delegates)
            Compare<int, int> _4s_1_1 = new Compare<int, int>(_3s_1);
            Compare<int, int> _4s_1_2 = new Compare<int, int>(_3s_2);
            Console.WriteLine("4s.1:'t" + Equate(_4s_1_1, _4s_1_2));
            // Test 4i.1 (same type delegates from different type instance-delegates)
            Compare<int, int> _4i_1_1 = new Compare<int, int>(_3i_1);
            Compare<int, int> _4i_1_2 = new Compare<int, int>(_3i_2);
            Console.WriteLine("4i.1:'t" + Equate(_4i_1_1, _4i_1_2));
            // Test 5s (same type delegates from different static-methods with same IL compilations)
            Compare<int> _5s_1 = Test;
            Compare<int> _5s_2 = Test2;
            Console.WriteLine("5s:'t" + Equate(_5s_1, _5s_2));
            // Test 5i (same type delegates from different instance-methods with same IL compilations)
            Program _5i_0 = new Program();
            Compare<int> _5i_1 = _5i_0.Test3;
            Compare<int> _5i_2 = _5i_0.Test4;
            Console.WriteLine("5i:'t" + Equate(_5i_1, _5i_2));
            Console.WriteLine();
            Console.WriteLine("Enter to close...");
            Console.ReadLine();
        }
        public static int Test(int l, int r) { return 0; }
        public static int Test2(int l, int r) { return 0; }
        public int Test3(int l, int r) { return 0; }
        public int Test4(int l, int r) { return 0; }
        // FIX ME!-----------------------------------------------------
        public static bool Equate(System.Delegate a, System.Delegate b)
        {
            // standard equality
            if (a == b)
                return true;
            // null
            if (a == null || b == null)
                return false;
            // compiled method body
            if (a.Target != b.Target)
                return false;
            byte[] a_body = a.Method.GetMethodBody().GetILAsByteArray();
            byte[] b_body = b.Method.GetMethodBody().GetILAsByteArray();
            if (a_body.Length != b_body.Length)
                return false;
            for (int i = 0; i < a_body.Length; i++)
            {
                if (a_body[i] != b_body[i])
                    return false;
            }
            return true;
        }
    }
}

以下是当前失败的测试:2s, 2i, 4s, 4i, 4s.1, 4i.1

比较 C# 中的委托

这是这些测试用例的解决方案。您必须通过委托分配消除所有原因开销。只需不断检查目标是否是委托。

public static bool Equate(System.Delegate a, System.Delegate b)
{
    // ADDED THIS --------------
    // remove delegate overhead
    while (a.Target is Delegate)
        a = a.Target as Delegate;
    while (b.Target is Delegate)
        b = b.Target as Delegate;
    // standard equality
    if (a == b)
        return true;
    // null
    if (a == null || b == null)
        return false;
    // compiled method body
    if (a.Target != b.Target)
        return false;
    byte[] a_body = a.Method.GetMethodBody().GetILAsByteArray();
    byte[] b_body = b.Method.GetMethodBody().GetILAsByteArray();
    if (a_body.Length != b_body.Length)
        return false;
    for (int i = 0; i < a_body.Length; i++)
    {
        if (a_body[i] != b_body[i])
            return false;
    }
    return true;
}

因此,让我们拿出第一个失败的测试用例并孤立地查看它。 为了清楚起见,我将更改一些变量名称。

Compare<int> firstComparer = Test;
Compare<int> secondComparer = Test;
Compare<int> thirdComparer = new Compare<int>(firstComparer);
Compare<int> fourthComparer = new Compare<int>(secondComparer);
Console.WriteLine("2s:'t" + Equate(thirdComparer, fourthComparer));

现在,当我们进行比较时,让我们看看这四个代表中的每一个的目标和方法:

variable       | Target         | Method
firstComparer  | null           | Test
secondComparer | null           | Test
thirdComparer  | firstComparer  | Invoke
fourthComparer | secondComparer | Invoke

现在,从技术上讲,thirdComparerTarget不是变量firstComparer,目标是该变量的值;它是firstComparer在评估new Compare<int>(firstComparer)时指向的委托,但希望你明白了。

那么,为什么第三和第四代表不平等呢? 因为他们有完全不同的目标。 这两个委托都调用两个不同实例的相同方法。 现在碰巧是,在这种特殊情况下,当您调用它们时,这两个完全不同的实例将执行相同的操作,但不一定这种情况。

因此,如果您希望您的平等支持这一点,您需要以某种方式能够确定各个委托的目标是否指向相同的变量或指向等效值。 写一些对这一个案例有用的东西可能是可行的;写一些涵盖一般情况的东西可能是不可能的。 在一般情况下,您不一定知道两个对象是否应被视为"等效"。 你可以拉出一个IEqualityComparer<T>.Default,如果这个实现是足够的。 它可能在这里有效,但并非所有类型都以您可能希望的方式覆盖平等。

最好的解决方案可能是首先避免这个问题。 与其让委托在调用时调用其他不同的委托,但它们本身指向相同的值,不如删除该间接层。 如果我们将上面的测试用例调整为:

Compare<int> firstComparer = Test;
Compare<int> secondComparer = Test;
Compare<int> thirdComparer = firstComparer;
Compare<int> fourthComparer = secondComparer;
Console.WriteLine("2s:'t" + Equate(thirdComparer, fourthComparer));

然后测试通过,因为第三个和第四个代表彼此不同,但与firstComparersecondComparer所指向的代表相同,两者具有相同的目标和方法。

您的所有其他测试用例,

而不仅仅是这一个测试用例,都有完全相同的问题,因此我认为没有理由单独查看每个测试用例。 所有这些都在添加这个额外的间接层,这就是为什么它们不相等的原因。