通用函数中的equals()和==

时间:2012-09-12 18:24:53

标签: c# equals

我正在对各种类型的集合操作进行比较。

所以我有一个通用类

 public class Comparer<T, Tid>
...
     public bool Equals(T x, T y)
       {
          var xid = m_idfunc(x);
           var yid = m_idfunc(y);
           return (Tid)xid == (Tid)yid;
       }

其中m_idfunc是传递给Comparer构造函数的lambda,它是

Func<T,Tid>

我用Tid = string创建一个比较器。我进入等于函数xid = string1,yid = string2

如果string1和string 2相同(“foo”和“foo”说)

xid == yid

产生错误

(Tid)xid == (Tid)yid

也会产生错误(它不应该是必要的 - 我只是绝望了)

继承我的即时窗口 - 在返回xid == yid行暂停

yid.GetType() == typeof(string)
true
xid.GetType() == typeof(string)
true
xid==yid
false
(string)xid==(string)yid
true
xid.Equals(yid)
true

怎么回事?

4 个答案:

答案 0 :(得分:3)

有趣的是,可能按照您希望的方式工作。这是一个例子:

using System;
using System.Text;

namespace ConsoleApplication1 {

    class Program {

        public static void Main()  {
            string myString = "1";
            object objectString = "1";
            string myCopiedString = string.Copy(myString);
            string internedString = string.Intern(myCopiedString);

            Console.WriteLine(myString); //1
            Console.WriteLine(objectString); //1
            Console.WriteLine(myCopiedString); //1
            Console.WriteLine(internedString); //1

            Console.Write(objectString == myString); //true
            Console.Write(objectString == "1"); //true
            Console.Write(objectString == myCopiedString); //!!!FALSE!!!!
            Console.Write(objectString == internedString); //true
            Console.Write(objectString == SomeMethod()); //!!!FALSE!!!
            Console.Write(objectString == SomeOtherMethod()); //true
        }

        public static string SomeMethod() {
            StringBuilder sb = new StringBuilder();
            return sb.Append("1").ToString();
        }

        public static string SomeOtherMethod() {
            return "1".ToString();
        }        
    }
}

可能工作的原因是由于字符串实习。所以,这绝对是值得注意的,因为它在测试时实际上可以工作,但是根据实现情况,它可能会突然中断。

在您的情况下,您需要确定您是否关心引用相等或“值”相等。 ==是引用相等,这又取决于字符串是否被实现可能为真。我怀疑你确实想在你的函数中使用EqualityComparer<T>.Default.Equals

如果你在VS中打开它,你会看到编译器警告:“可能的非预期参考比较;要获得值比较,请将左侧投射到“string”类型。但是,在你的情况下,编译器不能警告你,因为据它所知,类型是对象,它不知道一个或两个都是字符串。

答案 1 :(得分:1)

我最初的假设是,因为它是泛型,所以它不能在幕后对字符串进行值转换。我想把一个支持这个的例子放在一起。 :) 我必须做一些假设才能将这些东西放在一起,所以我的例子可能不是百分之百。 (我使用的代码位于底部)

当我刚刚

时,我无法得到任何东西来编译
class Comparer<T, TId>
{
    private readonly Func<T, TId> m_idfunc;
    public Comparer(Func<T, TId> idFunc)
    {
        m_idfunc = idFunc;
    }

    public bool Equals(T x, T y)
    {
        var xid = m_idfunc(x);
        var yid = m_idfunc(y);
        return (TId)xid == (TId)yid;
    }
}

我找到https://stackoverflow.com/a/390919/156708并将类声明修改为

class Comparer<T, TId> where TId : class 

并编译。第1步。

我将Equals功能设置为

public bool Equals(T x, T y)
{
    var xid = m_idfunc(x);
    var yid = m_idfunc(y);
    return (TId)xid == (TId)yid;
}

结果是False(参见xid | yid中生成值的完整代码)。符合我对Generics有所帮助的假设。还不够,需要看看如果删除了Generics方面会发生什么。

Comparer类更改为

class Comparer<T>
{
    private readonly Func<T, string> m_idfunc;
    public Comparer(Func<T, string> idFunc)
    {
        m_idfunc = idFunc;
    }

    public bool Equals(T x, T y)
    {
        var xid = m_idfunc(x);
        var yid = m_idfunc(y);
        return xid == yid;
    }
}

返回True

我不是百分之百,但我的假设是基于==类的string运算符进行值检查而不是引用检查。使用泛型时,可能只设置参考检查(没有挖到IL以查看它在那里做了什么),如果内存中的字符串位置不相同,那么它将返回false。 (我有点狡猾的细节,因为我不熟悉它们,只是一个似乎有效的工作假设)

我的完整示例代码包含泛型。

using System;
namespace ConsoleApplication1
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            var compare = new Comparer<Example, string>(example => example.id(example));
            var ex1 = new Example();
            var ex2 = new Example();
            Console.WriteLine(compare.Equals(ex1, ex2));
            Console.ReadLine();
        }
        class Example
        {
            public string id(Example example)
            {
                return new string(new [] {'f', 'o', 'o'});
            }
        }
        class Comparer<T, TId> where TId : class 
        {
            private readonly Func<T, TId> m_idfunc;
            public Comparer(Func<T, TId> idFunc)
            {
                m_idfunc = idFunc;
            }

            public bool Equals(T x, T y)
            {
                var xid = m_idfunc(x);
                var yid = m_idfunc(y);
                return (TId)xid == (TId)yid;
            }
        }
    }
}

希望有所帮助......而且我的推理并不是非常错误。 :)

答案 2 :(得分:0)

我认为,在EqualityComparer<TId>内使用Comparer<T, Tid>更为正确。此外,我将使用interface来获取标识符,而不是委托:

interface IObjectWithId<T>
{
    T Id { get; }
}

class IdEqualityComparer<T, TId> : EqualityComparer<T>
    where T : IObjectWithId<TId>
{
    public override bool Equals(T x, T y)
    {
        return EqualityComparer<TId>.Default.Equals(x.Id, y.Id);
    }

    public override int GetHashCode(T obj)
    {
        return EqualityComparer<TId>.Default.GetHashCode(obj.Id);
    }
}

class A : IObjectWithId<string>
{
    public string Id { get; set; }
}

用法:

var a = new A { Id = "foo" };
var b = new A { Id = "foo" };
var c = new A { Id = "bar" };

var comparer = new IdEqualityComparer<A, string>();

Console.WriteLine(comparer.Equals(a, b)); // true
Console.WriteLine(comparer.Equals(a, c)); // false

答案 3 :(得分:0)

C运算符“==”有两个非常不同的含义。如果编译器可以静态地确定这样的方法适用于操作数类型,它可以调用特定于类型的重载等于运算符方法,或者它可以在操作数之间执行引用比较,如果两者都可以已知操作数是引用类型,并且可能存在可由两个操作数引用的对象。对于大多数类型,只能进行一种比较;值类型不支持引用比较,并且大多数引用类型不会重载相等运算符。但是,有一个共同的类可以支持两种类型的比较:System.String

vb.net语言通过仅允许=运算符用于超载它的类型来避免歧义。对于参考比较,需要Is运算符。如果有人试图在vb.net中编写代码,那么=运算符将不允许使用类约束泛型。可以使用Is运算符,但无论操作数是否超载=,它都会检查引用相等。

实际上,在C#中,假设你的泛型类型有一个类约束(如果没有它,==运算符将不起作用),编译器只能在泛型类型上使用重载的相等运算符。类型被约束为运算符过载的类型。由于您没有将泛型类型参数约束为string(实际上,因为string已被密封,编译器将不允许这样的约束)编译器无法使用{{1}等于运算符的重载。因此,它使用它知道在类约束泛型上可用的相等运算符的版本 - 引用相等(相当于vb.net中的string运算符)。