5

我正在为各种类型的集合操作制作一个比较器。

所以我有一个通用类

 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

4 回答 4

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 中运行 open this,你会看到编译器警告:“Possible unintended reference comparison; 要获得值比较,请将左侧转换为类型“字符串”。但是,在您的情况下,编译器无法警告您,因为据它所知,类型是对象,它不知道其中一个或两个都是字符串。

于 2012-09-12T19:28:24.053 回答
1

我最初的假设是,因为它是泛型的,所以它不能像对字符串所做的那样在幕后引用值转换。我想整理一个支持这一点的例子。:) 我不得不做出一些假设来为此组合一些东西,所以我的例子可能不是 100% 的。(我使用的代码在底部)

当我刚刚编译时,我无法编译任何东西

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 中生成值的完整代码)。符合我的假设,即泛型对此有所帮助。还不够,需要看看如果删除了泛型方面会发生什么。

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

我对此不是 100% 的,但我的假设是基于类的==操作员进行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;
            }
        }
    }
}

希望这会有所帮助……而且我的推理并没有大错特错。:)

于 2012-09-12T19:30:47.877 回答
0

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

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
于 2012-09-12T19:18:13.400 回答
0

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

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

实际上,在 C# 中,假设您对泛型类型有类约束(==没有它,运算符将无法工作),如果类型被约束为运算符重载。由于您没有将泛型类型参数限制为string(实际上,由于string是密封的,因此编译器不会允许这样的约束),因此编译器无法使用string等式运算符的重载。因此,它使用它知道在类约束泛型引用相等(相当于Isvb.net 中的运算符)上可用的相等运算符版本。

于 2012-09-12T23:54:57.023 回答