在C#中比较代表

时间:2015-06-17 17:29:51

标签: c# delegates

我需要比较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

2 个答案:

答案 0 :(得分:1)

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

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;
}

答案 1 :(得分:0)

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

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

现在,从技术上讲,Target的{​​{1}}不是变量 thirdComparer,目标是该变量的值;它是firstComparer在评估firstComparer时指向的代理人,但希望您明白这一点。

那么为什么第三和第四位代表不相同呢?因为他们有完全不同的目标。这两个代理都调用两个不同实例的相同方法。现在碰巧是这两个完全不同的实例在你调用它们的时候会做同样的事情,在这种特殊情况下,但不一定需要。< / p>

因此,如果您希望您的相等性支持此功能,那么您需要能够确定各个代理的目标是指向相同的变量还是指向等效值。写一些适用于这个案例的东西可能是可行的;写一些涵盖一般情况的内容可能是不可能的。在一般情况下,您不一定知道两个对象是否应被视为“等效”。如果该实现足够,您可以提取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)); firstComparer所指向的委托相同,它们都具有相同的目标和方法。 / p>

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