2013-07-09 58 views
2

爲了進行單元測試,我經常必須重寫EqualsGetHashCode方法。在此之後我的課開始是這樣的:有沒有辦法在Equals和GetHashCode中減少樣板代碼的數量?

public class TestItem 
{ 
    public bool BoolValue { get; set; } 

    public DateTime DateTimeValue { get; set; } 

    public double DoubleValue { get; set; } 

    public long LongValue { get; set; } 

    public string StringValue { get; set; } 

    public SomeEnumType EnumValue { get; set; } 

    public decimal? NullableDecimal { get; set; } 

    public override bool Equals(object obj) 
    { 
     var other = obj as TestItem; 

     if (other == null) 
     { 
      return false; 
     } 

     if (object.ReferenceEquals(this, other)) 
     { 
      return true; 
     } 

     return this.BoolValue == other.BoolValue 
      && this.DateTimeValue == other.DateTimeValue 
      && this.DoubleValue == other.DoubleValue // that's not a good way, but it's ok for demo 
      && this.EnumValue == other.EnumValue 
      && this.LongValue == other.LongValue 
      && this.StringValue == other.StringValue 
      && this.EnumValue == other.EnumValue 
      && this.NullableDecimal == other.NullableDecimal; 
    } 

    public override int GetHashCode() 
    { 
     return this.BoolValue.GetHashCode() 
      ^this.DateTimeValue.GetHashCode() 
      ^this.DoubleValue.GetHashCode() 
      ^this.EnumValue.GetHashCode() 
      ^this.LongValue.GetHashCode() 
      ^this.NullableDecimal.GetHashCode() 
      ^(this.StringValue != null ? this.StringValue.GetHashCode() : 0); 
    } 
} 

雖然它不是很難做到這一點,一次又一次它很無聊而且容易出錯保持EqualsGetHashCode相同的字段列表。有沒有什麼方法可以列出只用於平等檢查和散列碼函數的filelds? Equals和GetHashCode應該根據這個設置列表來實現。

在我的想象的配置和這樣的設置列表的使用可能看起來像

public class TestItem 
{ 
    // same properties as before 

    private static readonly EqualityFieldsSetup Setup = new EqualityFieldsSetup<TestItem>() 
     .Add(o => o.BoolValue) 
     .Add(o => o.DateTimeValue) 
     // ... and so on 
     // or even .Add(o => o.SomeFunction()) 

    public override bool Equals(object obj) 
    { 
     return Setup.Equals(this, obj); 
    } 

    public override int GetHashCode() 
    { 
     return Setup.GetHashCode(this); 
    } 
} 

有自動在Java中實現hashCodeequalsproject lombok例如一種方式。我不知道是否有任何東西可以用來減少C#提供的樣板代碼。

+0

用適當的對象狀態表示覆蓋tostring,其hashcode將基於該輸入。 – terrybozzio

+1

@terrybozzio'ToString()'比較?永遠不要這樣做。首先,這是昂貴的。比較功能應該很快。其次,它甚至沒有砍掉樣板,它只是把它移到其他地方。 –

+0

@Mike:我懷疑它。你上面的GetHashCode的實現非常具體。我可能完全不滿意這個實現。與Equals相同(在較小程度上)。 –

回答

2

我做了一些研究,發現並沒有完全是我想要的幾部分組成:

而且還幾個相關的討論:具有成員明確配置列表

到目前爲止,想法似乎是唯一的。我實施了我自己的圖書館https://github.com/alabax/YetAnotherEqualityComparer。它比TylerOhlsen建議的代碼更好,因爲它不提取成員,它使用EqualityComparer<T>來比較成員。

現在的代碼如下所示:

public class TestItem 
{ 
    private static readonly MemberEqualityComparer<TestItem> Comparer = new MemberEqualityComparer<TestItem>() 
     .Add(o => o.BoolValue) 
     .Add(o => o.DateTimeValue) 
     .Add(o => o.DoubleValue) // IEqualityComparer<double> can (and should) be specified here 
     .Add(o => o.EnumValue) 
     .Add(o => o.LongValue) 
     .Add(o => o.StringValue) 
     .Add(o => o.NullableDecimal); 

    // property list is the same 

    public override bool Equals(object obj) 
    { 
     return Comparer.Equals(this, obj); 
    } 

    public override int GetHashCode() 
    { 
     return Comparer.GetHashCode(this); 
    } 
} 

另外,MemberEqualityComparer實現IEqualityComparer<T>並遵循其語義:它可以成功地比較default(T)這可能是null引用類型和Nullables。

UPDATE:的工具,可以解決成員基於IEqualityComparer<T>而且這些能夠提供複合IComparer<T>創造了同樣的問題!

1

我認爲在C#中實現與Lombok幾乎完全相同的東西是可能的,但我目前並沒有那麼雄心勃勃。

我相信這就是你所追求的(幾乎完全如你所描述的那樣)。這個實現確實將所有的值類型都放到了對象中,所以它不是最有效的實現,但它應該足夠滿足你的單元測試的目的。

public class EqualityFieldsSetup<T> 
    where T : class 
{ 
    private List<Func<T, object>> _propertySelectors; 

    public EqualityFieldsSetup() 
    { 
     _propertySelectors = new List<Func<T, object>>(); 
    } 

    public EqualityFieldsSetup<T> Add(Func<T, object> propertySelector) 
    { 
     _propertySelectors.Add(propertySelector); 
     return this; 
    } 

    public bool Equals(T objA, object other) 
    { 
     //If both are null, then they are equal 
     // (a condition I think you missed) 
     if (objA == null && other == null) 
      return true; 

     T objB = other as T; 

     if (objB == null) 
      return false; 

     if (object.ReferenceEquals(objA, objB)) 
      return true; 

     foreach (Func<T, object> propertySelector in _propertySelectors) 
     { 
      object objAProperty = propertySelector.Invoke(objA); 
      object objBProperty = propertySelector.Invoke(objB); 

      //If both are null, then they are equal 
      // move on to the next property 
      if (objAProperty == null && objBProperty == null) 
       continue; 

      //Boxing requires the use of Equals() instead of '==' 
      if (objAProperty == null && objBProperty != null || 
       !objAProperty.Equals(objBProperty)) 
       return false; 
     } 

     return true; 
    } 

    public int GetHashCode(T obj) 
    { 
     int hashCode = 0; 

     foreach (Func<T, object> propertySelector in _propertySelectors) 
     { 
      object objProperty = propertySelector.Invoke(obj); 

      if (objProperty != null) 
       hashCode ^= objProperty.GetHashCode(); 
     } 

     return hashCode; 
    } 
} 
+0

太棒了,建立這樣的工具並不難!這裏的問題是你必須將lambda返回值轉換爲'object'。我建議使用'EquailtyComparer '來比較成員值。 – Mike

+0

儘管屬性存儲在類型爲object的變量中,但Equals方法仍將使用派生類型的(可能重寫的)Equals方法。我在這裏寫的功能與上述相同。唯一的區別是值類型隱式地裝入對象中。 – TylerOhlsen

+0

而你的lambda中的對象投射可以是隱含的。在句法上,這個答案的工作原理與您在問題中提出的完全相同。從功能上來說,它是等價的(需要額外的步驟來填充值類型)。 – TylerOhlsen

相關問題