Interessante domanda. Le specifiche definiscono che i metodi Equals
e GetHashcode
(si noti che l'errore di battitura nella specifica!) Si comporteranno per istanze dello stesso tipo, tuttavia l'implementazione non è definita. Come succede, l'attuale compilatore MS C# implementa questo usando numeri magici come un seme di -1134271262
e un moltiplicatore di -1521134295
. Ma questo non fa parte della specifica. Teoricamente questo potrebbe cambiare radicalmente tra le versioni del compilatore C# e risponderebbe comunque a ciò che è necessario. Quindi se i 2 assembly non sono compilati dallo stesso compilatore, non c'è alcuna garanzia. In effetti, sarebbe "valido" (ma improbabile) che il compilatore pensasse a un nuovo valore seme ogni volta che compila.
Personalmente, guarderei le tecniche IL o Expression
per farlo. Confrontando oggetti dalla forma simile a livello di membro per nome è abbastanza facile fare con Expression
.
Per info, ho anche guardato come mcs
(il compilatore Mono) implementa GetHashCode
, e è diverso; invece di seme e moltiplicatore, utilizza una combinazione di seme, xor, moltiplicatore, turni e aggiunte. Quindi lo stesso tipo compilato da Microsoft e Mono avrà molto diverso GetHashCode
.
static class Program {
static void Main() {
var obj = new { A = "abc", B = 123 };
System.Console.WriteLine(obj.GetHashCode());
}
}
- Mono: -2077468848
- Microsoft: -617335881
Fondamentalmente, non credo che si può garantire questo.
ne dite:
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
class Foo
{
public string A { get; set; }
public int B; // note a field!
static void Main()
{
var obj1 = new { A = "abc", B = 123 };
var obj2 = new Foo { A = "abc", B = 123 };
Console.WriteLine(MemberwiseComparer.AreEquivalent(obj1, obj2)); // True
obj1 = new { A = "abc", B = 123 };
obj2 = new Foo { A = "abc", B = 456 };
Console.WriteLine(MemberwiseComparer.AreEquivalent(obj1, obj2)); // False
obj1 = new { A = "def", B = 123 };
obj2 = new Foo { A = "abc", B = 456 };
Console.WriteLine(MemberwiseComparer.AreEquivalent(obj1, obj2)); // False
}
}
public static class MemberwiseComparer
{
public static bool AreEquivalent(object x, object y)
{
// deal with nulls...
if (x == null) return y == null;
if (y == null) return false;
return AreEquivalentImpl((dynamic)x, (dynamic)y);
}
private static bool AreEquivalentImpl<TX, TY>(TX x, TY y)
{
return AreEquivalentCache<TX, TY>.Eval(x, y);
}
static class AreEquivalentCache<TX, TY>
{
static AreEquivalentCache()
{
const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
var xMembers = typeof(TX).GetProperties(flags).Select(p => p.Name)
.Concat(typeof(TX).GetFields(flags).Select(f => f.Name));
var yMembers = typeof(TY).GetProperties(flags).Select(p => p.Name)
.Concat(typeof(TY).GetFields(flags).Select(f => f.Name));
var members = xMembers.Intersect(yMembers);
Expression body = null;
ParameterExpression x = Expression.Parameter(typeof(TX), "x"),
y = Expression.Parameter(typeof(TY), "y");
foreach (var member in members)
{
var thisTest = Expression.Equal(
Expression.PropertyOrField(x, member),
Expression.PropertyOrField(y, member));
body = body == null ? thisTest
: Expression.AndAlso(body, thisTest);
}
if (body == null) body = Expression.Constant(true);
func = Expression.Lambda<Func<TX, TY, bool>>(body, x, y).Compile();
}
private static readonly Func<TX, TY, bool> func;
public static bool Eval(TX x, TY y)
{
return func(x, y);
}
}
}
Nota: ho aggiunto un operatore di confronto 'Expression' a base di membro a membro - potrebbe essere utile –
brillante, grazie. –