C# 中的动态相等
Dynamic equality in C#
假设我有以下 class:
public class TsvDataModel : IEquatable<TsvDataModel>
{
public int ElementId { get; set; }
public string Location { get; set; }
public string RoomKey { get; set;
public decimal Area { get; set; }
public bool Equals(TsvDataModel other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return ElementUid == other.ElementUid && Location == other.Location && RoomKey == other.RoomKey &&
SubType == other.SubType;
}
// current equality comparison implementation
public override bool Equals(object obj) => ReferenceEquals(this, obj) || (obj is TsvDataModel other && Equals(other));
public override int GetHashCode() => HashCode.Combine(ElementUid, Location, RoomKey, SubType);
}
我想要的是基于用户提供的属性创建动态相等性,即有时我只想通过 ElementIds 进行比较,有时与 include/exclude 其他属性进行比较。我不想创建所有可能的组合。我尝试使用反射通过 dynamic
类型获取属性,我可以获得值和类型,但比较不起作用。
我更愿意将属性作为 属性 名称(字符串)的列表传递。
我想使用 IEqualityComparer
或 IEquitable
(我将此对象用作 HashSet<T>
)。
这里是 DynamicEqualityComparer
:
public class DynamicEqualityComparer<T> : IEqualityComparer<T>
{
private readonly IEnumerable<Func<T, object>> _propertySelectors;
public DynamicEqualityComparer(params Func<T, object>[] propertySelectors)
{
_propertySelectors = propertySelectors;
}
public bool Equals(T x, T y)
{
return _propertySelectors.All(propertySelector =>
object.Equals(propertySelector(x), propertySelector(y)));
}
public int GetHashCode(T obj)
{
var hashCode = new HashCode();
foreach (var propertySelector in _propertySelectors)
{
hashCode.Add(propertySelector((T)obj));
}
return hashCode.ToHashCode();
}
public static DynamicEqualityComparer<T> FromPropertyNames(IEnumerable<string> propertyNames)
{
IEnumerable<Func<T, object>> selectors = propertyNames.Select(propertyName =>
{
var property = typeof(T).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public);
if (property == null)
{
throw new Exception($"Unable to find a property named {propertyName} in type {typeof(T)}");
}
return new Func<T, object>(obj => property.GetValue(obj));
});
return new DynamicEqualityComparer<T>(selectors.ToArray());
}
}
用法:
var comparer = new DynamicEqualityComparer<TsvDataModel>(
model => model.Location,
model => model.RoomKey,
model => model.Area);
var hashset = new HashSet<TsvDataModel>(comparer);
或
var comparer = DynamicEqualityComparer<TsvDataModel>
.FromPropertyNames(new[] {"Location", "RoomKey", "Area"});
var hashset = new HashSet<TsvDataModel>(comparer);
此外,这里有一些单元测试:
[Fact]
public void Recognizes_Equal_Models()
{
var modelA = new TsvDataModel { ElementId = 2, Location = "x", RoomKey = "y", Area = 1.1m };
var modelB = new TsvDataModel { ElementId = 3, Location = "x", RoomKey = "y", Area = 1.1m };
var comparer = DynamicEqualityComparer<TsvDataModel>
.FromPropertyNames(new[] { "Location", "RoomKey", "Area" });
var hashset = new HashSet<TsvDataModel>(comparer);
hashset.Add(modelA);
hashset.Add(modelB);
// ElementId is ignored. Location, RoomKey, and Area are the same.
// So the comparer indicates that they are "equal."
Assert.Equal(1, hashset.Count);
}
[Fact]
public void Recognizes_Unequal_Models()
{
var modelA = new TsvDataModel { ElementId = 2, Location = "xy", RoomKey = "y", Area = 1.1m };
var modelB = new TsvDataModel { ElementId = 3, Location = "x", RoomKey = "y", Area = 1.1m };
var comparer = DynamicEqualityComparer<TsvDataModel>
.FromPropertyNames(new[] { "Location", "RoomKey", "Area" });
var hashset = new HashSet<TsvDataModel>(comparer);
hashset.Add(modelA);
hashset.Add(modelB);
// ElementId is ignored. RoomKey and Area are the same but Location is different.
// So the comparer indicates that they are not "equal."
Assert.Equal(2, hashset.Count);
}
假设我有以下 class:
public class TsvDataModel : IEquatable<TsvDataModel>
{
public int ElementId { get; set; }
public string Location { get; set; }
public string RoomKey { get; set;
public decimal Area { get; set; }
public bool Equals(TsvDataModel other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return ElementUid == other.ElementUid && Location == other.Location && RoomKey == other.RoomKey &&
SubType == other.SubType;
}
// current equality comparison implementation
public override bool Equals(object obj) => ReferenceEquals(this, obj) || (obj is TsvDataModel other && Equals(other));
public override int GetHashCode() => HashCode.Combine(ElementUid, Location, RoomKey, SubType);
}
我想要的是基于用户提供的属性创建动态相等性,即有时我只想通过 ElementIds 进行比较,有时与 include/exclude 其他属性进行比较。我不想创建所有可能的组合。我尝试使用反射通过 dynamic
类型获取属性,我可以获得值和类型,但比较不起作用。
我更愿意将属性作为 属性 名称(字符串)的列表传递。
我想使用 IEqualityComparer
或 IEquitable
(我将此对象用作 HashSet<T>
)。
这里是 DynamicEqualityComparer
:
public class DynamicEqualityComparer<T> : IEqualityComparer<T>
{
private readonly IEnumerable<Func<T, object>> _propertySelectors;
public DynamicEqualityComparer(params Func<T, object>[] propertySelectors)
{
_propertySelectors = propertySelectors;
}
public bool Equals(T x, T y)
{
return _propertySelectors.All(propertySelector =>
object.Equals(propertySelector(x), propertySelector(y)));
}
public int GetHashCode(T obj)
{
var hashCode = new HashCode();
foreach (var propertySelector in _propertySelectors)
{
hashCode.Add(propertySelector((T)obj));
}
return hashCode.ToHashCode();
}
public static DynamicEqualityComparer<T> FromPropertyNames(IEnumerable<string> propertyNames)
{
IEnumerable<Func<T, object>> selectors = propertyNames.Select(propertyName =>
{
var property = typeof(T).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public);
if (property == null)
{
throw new Exception($"Unable to find a property named {propertyName} in type {typeof(T)}");
}
return new Func<T, object>(obj => property.GetValue(obj));
});
return new DynamicEqualityComparer<T>(selectors.ToArray());
}
}
用法:
var comparer = new DynamicEqualityComparer<TsvDataModel>(
model => model.Location,
model => model.RoomKey,
model => model.Area);
var hashset = new HashSet<TsvDataModel>(comparer);
或
var comparer = DynamicEqualityComparer<TsvDataModel>
.FromPropertyNames(new[] {"Location", "RoomKey", "Area"});
var hashset = new HashSet<TsvDataModel>(comparer);
此外,这里有一些单元测试:
[Fact]
public void Recognizes_Equal_Models()
{
var modelA = new TsvDataModel { ElementId = 2, Location = "x", RoomKey = "y", Area = 1.1m };
var modelB = new TsvDataModel { ElementId = 3, Location = "x", RoomKey = "y", Area = 1.1m };
var comparer = DynamicEqualityComparer<TsvDataModel>
.FromPropertyNames(new[] { "Location", "RoomKey", "Area" });
var hashset = new HashSet<TsvDataModel>(comparer);
hashset.Add(modelA);
hashset.Add(modelB);
// ElementId is ignored. Location, RoomKey, and Area are the same.
// So the comparer indicates that they are "equal."
Assert.Equal(1, hashset.Count);
}
[Fact]
public void Recognizes_Unequal_Models()
{
var modelA = new TsvDataModel { ElementId = 2, Location = "xy", RoomKey = "y", Area = 1.1m };
var modelB = new TsvDataModel { ElementId = 3, Location = "x", RoomKey = "y", Area = 1.1m };
var comparer = DynamicEqualityComparer<TsvDataModel>
.FromPropertyNames(new[] { "Location", "RoomKey", "Area" });
var hashset = new HashSet<TsvDataModel>(comparer);
hashset.Add(modelA);
hashset.Add(modelB);
// ElementId is ignored. RoomKey and Area are the same but Location is different.
// So the comparer indicates that they are not "equal."
Assert.Equal(2, hashset.Count);
}