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 コンストラクターに渡されるラムダです。

Func<T,Tid>

Tid = string で比較子を作成します。equals 関数を取得します xid = string1, yid = string2

string1 と string 2 が同じ場合 ("foo" と "foo" と言います)

xid == yid

false を返す

(Tid)xid == (Tid)yid

また、falseを生成します(必要ないはずです-私は必死になりました)

これが私の即時ウィンドウです - 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

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.

If you run open this in VS you'll see the compiler warning: “Possible unintended reference comparison; to get a value comparison, cast the left hand side to type '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.

于 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 で値を生成するための完全なコードを参照してください)。ジェネリックがこれに関与しているという私の仮定に適合します。まだ十分ではありません。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

==私はこれについて 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

C演算子"=="には2つの非常に異なる意味があります。コンパイラがそのようなメソッドがオペランドタイプに適用可能であると静的に判断できる場合は、タイプ固有のオーバーロードされた等価演算子メソッドを呼び出すことができます。両方のオペランドが参照型であることがわかっている場合は、オペランド間の参照比較を実行できます。また、両方のオペランドで参照できるオブジェクトが存在する場合があります。ほとんどのタイプでは、1つのタイプの比較のみが可能です。値型は参照比較をサポートしておらず、ほとんどの参照型は等式演算子をオーバーロードしません。ただし、両方のタイプの比較をサポートする共通のクラスがありますSystem.String

vb.net言語は、=オーバーロードする型でのみ演算子を使用できるようにすることで、ここでのあいまいさを回避します。参照比較には、Is演算子が必要です。vb.netでコードを書き込もうとした場合=、クラスに制約のあるジェネリックスでの演算子の使用は許可されません。演算子を使用することもできますがIs、オペランドがオーバーロードするかどうかに関係なく、参照が等しいかどうかをチェックします=

現状では、C#では、ジェネリック型にクラス制約があると仮定すると(==演算子はそれなしでは機能しません)、コンパイラは、型が次の型に制約されている場合にのみ、ジェネリック型に対してオーバーロードされた等式演算子を使用できます。オペレーターが過負荷になっています。ジェネリック型パラメーターを制約しないためstring(実際、stringシールされているため、コンパイラーはそのような制約を許可しません)、コンパイラーがstring等式演算子のオーバーロードを使用する方法はありません。Isしたがって、クラスに制約のあるジェネリック参照の等式( vb.netの演算子と同等)で使用できることがわかっているバージョンの等式演算子を使用します。

于 2012-09-12T23:54:57.023 に答える
0

EqualityComparer<TId>内部で使用する方が正しいと思います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 に答える