简体   繁体   English

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

[英]equals() and == in a generic function

I am making a comparer for set operation on various types. 我正在对各种类型的集合操作进行比较。

So I have a generic class 所以我有一个泛型类

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

Where m_idfunc is a lambda passed in to the Comparer constructor, it is 其中m_idfunc是传递给Comparer构造函数的lambda,它是

Func<T,Tid>

I create a comparer with Tid = string. 我用Tid = string创建了一个比较器。 I get in the equals function xid = string1, yid = string2 我进入等于函数xid = string1,yid = string2

If string1 and string 2 are the same ("foo" and "foo" say) 如果string1和string 2相同(“foo”和“foo”说)

xid == yid

yields false 产生错误

(Tid)xid == (Tid)yid

also yields false (it should not be necessary - I was just getting desperate) 也产生错误(它不应该是必要的 - 我只是变得绝望)

heres my immediate window - paused on the return xid == yid line 继承我的直接窗口 - 暂停返回xid == yid行

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

Whats going on? 这是怎么回事?

What's interesting about this is that it might just work the way you want it to. 有趣的是,它可能会按照你想要的方式工作。 Here's an example: 这是一个例子:

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

The reason why it might work is due to string interning. 可能起作用的原因是由于字符串实习。 So, this is definitely one to watch out for, because it can actually work when you test it, but depending on the implementation, it might suddenly break. 所以,这绝对是值得注意的,因为它在测试时实际上可以工作,但是根据实现情况,它可能会突然中断。

In your case, you need to determine whether you care about Reference equality or "value" equality. 在您的情况下,您需要确定您是否关心引用相等或“值”相等。 == is reference equality, which again, depending on whether or not the string is interned may be true. ==是引用相等,再次,取决于字符串是否被实现可能是真的。 I suspect you actually want to use EqualityComparer<T>.Default.Equals in your function. 我怀疑你真的想在你的函数中使用EqualityComparer<T>.Default.Equals

If you run open this in VS you'll see the compiler warning: “Possible unintended reference comparison; 如果你在VS中打开它,你会看到编译器警告:“可能的非预期参考比较; to get a value comparison, cast the left hand side to type 'string'”. 要获得值比较,请将左侧投射到“string”类型。 In your case however, the compiler can't warn you, because as far as it knows, the types are objects, it doesn't know that one or both are string. 但是,在你的情况下,编译器不能警告你,因为据它所知,类型是对象,它不知道一个或两个都是字符串。

My initial assumption was that because it's generics, it can't do a reference to value conversion behind the scenes that it does for strings. 我最初的假设是,因为它是泛型,它不能在幕后对字符串进行值转换。 I wanted to put together an example that supported this. 我想把一个支持这个的例子放在一起。 :) I had to make a few assumptions to put something together for this so my example may not be 100% on. :)我必须做一些假设才能将这些东西放在一起,所以我的例子可能不是百分之百。 (code I used is at the bottom) (我用的代码在底部)

I wasn't able to get anything to compile when I just had 我刚才有时无法编译任何东西

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

I found https://stackoverflow.com/a/390919/156708 and modifed the class declaration to be 我发现https://stackoverflow.com/a/390919/156708并修改了类声明

class Comparer<T, TId> where TId : class 

and it compiled. 并编译。 Step 1. 步骤1。

I set up the Equals function as 我将Equals函数设置为

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

And the result is False (see full code for generation of value in xid|yid). 结果为False (请参阅xid | yid中生成值的完整代码)。 Fitting my assumption that Generics has a hand in this. 符合我对Generics有所帮助的假设。 Not enough yet, need to see what happens if the Generics aspect is removed. 还不够,需要看看如果删除了Generics方面会发生什么。

Changing the Comparer class to be 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;
    }
}

returns True . 返回True

I'm not 100% on this, but my assumption is based on the fact that the == operator of the string class does a value check instead of a reference check. 我不是100%,但我的假设是基于这样一个事实,即string类的==运算符执行值检查而不是引用检查。 When using generics, it is likely setting up to only do reference checks (haven't dug into the IL to see what it's doing there), and if the string locations in memory are not the same then it will return false. 使用泛型时,可能只设置参考检查(没有挖到IL以查看它在那里做了什么),如果内存中的字符串位置不相同,那么它将返回false。 (I'm kinda winging the details, as I don't know them prezactly, just a working hypothesis that seems to be working) (我有点狡猾的细节,因为我不熟悉它们,只是一个看起来有效的工作假设)

My complete sample code with generics is below. 我的完整示例代码包含泛型如下。

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

Hope that helps... and that I'm not terribly wrong on my reasoning. 希望有所帮助......而且我的理由并非完全错误。 :) :)

I think, it is more correct to use EqualityComparer<TId> inside Comparer<T, Tid> . 我认为,在Comparer<T, Tid>使用EqualityComparer<TId>更为正确。 Besides, instead of delegate I would use interface to get identifiers: 此外,我将使用接口来获取标识符,而不是委托:

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

Usage: 用法:

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

The C operator "==" has two very different meanings. C运算符“==”有两个非常不同的含义。 It can either invoke a type-specific overloaded equality-operator method if the compiler can statically determine that such a method is applicable to the operand types, or it can perform a reference comparison between the operands, if both operands are known to be reference types and there may exist an object which could be referred to by both operands. 如果编译器可以静态地确定这样的方法适用于操作数类型,它可以调用特定于类型的重载等于运算符方法,或者如果已知两个操作数都是引用类型,它可以执行操作数之间的引用比较并且可能存在可由两个操作数引用的对象。 For most types, only one type of comparison would be possible; 对于大多数类型,只能进行一种比较; value types do not support reference comparison, and most reference types do not overload the equality operator. 值类型不支持引用比较,并且大多数引用类型不会重载相等运算符。 There is, however, a common class which would support both types of comparison: System.String . 但是,有一个公共类可以支持两种类型的比较: System.String

The vb.net language avoids ambiguity here by only allowing the = operator to be used on types which overload it. vb.net语言通过仅允许在运算符重载的类型上使用=运算符来避免歧义。 For reference comparisons, the Is operator is required. 对于参考比较,需要Is运算符。 If one were to attempt to write your code in vb.net, the = operator would not be permitted on class-constrained generics. 如果有人试图在vb.net中编写代码,则不允许在类约束泛型上使用=运算符。 One could use the Is operator, but it would check for reference equality regardless of whether the operands overload = . 可以使用Is运算符,但无论操作数是否= ,它都会检查引用相等性。

As it is, in C#, assuming you have a class constraint on your generic type (the == operator won't work without it), the compiler can only use an overloaded equality operator on a generic type if the type is constrained to one for which the operator is overloaded. 实际上,在C#中,假设您的泛型类型有一个类约束(如果没有它, ==运算符将无法工作),编译器只能在泛型类型上使用重载的相等运算符(如果类型约束为一个)操作员过载的。 Since you don't constrain your generic type parameter to string (indeed, since string is sealed, the compiler won't allow such a constraint) there's no way the compiler can use the string overload of the equality operator. 由于您没有将泛型类型参数约束为string (实际上,因为string是密封的,编译器不允许这样的约束)编译器无法使用相等运算符的string重载。 Thus, it uses the version of the equality operator that it knows is available on a class-constrained generic--reference equality (equivalent to the Is operator in vb.net). 因此,它使用它知道在类约束泛型上可用的相等运算符的版本 - 引用相等(相当于vb.net中的Is运算符)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM