2011-09-20 63 views
5

創建一個帶有Name屬性的項目的簡單列表(UniqueList),該屬性只能包含唯一項目(定義爲具有不同的名稱)。在UniqueList類型的約束可以是一個接口:類型參數的約束:接口與抽象類

interface INamed 
{ 
    string Name { get;} 
} 

或抽象類:

public abstract class NamedItem 
{ 
    public abstract string Name { get; } 
} 

所以UniqueList可以是:

class UniqueList<T> : List<T> where T : INamed 

class UniqueList<T> : List<T> where T : NamedItem 

類函數AddUni闕:

public T AddUnique(T item) 
{ 
    T result = Find(x => x.Name == item.Name); 
    if (result == default(T)) 
    { 
     Add(item); 
     return item; 
    } 
    return result; 
} 

如果類類型約束是基於接口上,編譯結果

Error: Operator '==' cannot be applied to operands of type 'T' and 'T' 

在該行

if (result == default(T)) 

一切都很好,如果我基地UniqueList於抽象類。有什麼想法嗎?

+1

你爲什麼不使用'HashSet的'這個?是不是你想要的 – BrokenGlass

+0

如果T是一個值類型,當沒有找到任何東西時,Find將返回'default(T)'?這意味着'default(T)'不被允許作爲一個值,這可能不是你的意圖。 –

+1

@BrokenGlass但他沒有創建一組字符串,他創建了一組命名的東西。他真的想要一個'的HashSet '有'的IEqualityComparer ' – phoog

回答

5

這是因爲接口可以應用於一個值類型的結構。爲了使其與接口工作擴展這樣的約束:

class UniqueList<T> : List<T> where T : INamed, class 

,將確保你將不能夠通過一個結構作爲T因此default(T)將評估爲null這是你所期望的。


另外,我建議您概括一個UniqueList位,允許不同類型的唯一密鑰:

interface IUnique<TKey> 
{ 
    TKey UniqueKey { get;} 
} 

class UniqueList<TItem,Tkey> : List<TItem> where TItem : IUnique<TKey>, class 

然後INamed接口,可以很容易地聲明:

interface INamed : IUnique<string> 
{ 
    string Name { get;} 
} 

UniqueKeyName將在實現類中明確地實現以防止不必要的事實)公共班級成員。

1

是的。使用抽象類爲您提供==運算符的System.Object實現,而使用接口並不能保證編譯器該類型將支持==運算符,因爲它可能是一個沒有實現的結構。如果您將class約束添加到您的類型參數,它應該編譯。

1

該接口不提供Equals的實現,因此您無法比較這些項目。

順便說一下,你好像要重新實現HashSet,請考慮使用官方提供的類來代替。

+0

不要忘了'Equals'方法是不一樣的''==操作符 – phoog

+0

重:該接口沒有提供Equals的實施;我不確定你在這裏的措辭。似乎運營商不能保證存在,但Equals方法應該可行,不是嗎?我想用result.Equals(default(T))替換「result == default(T)」將會完成Ol'Badger試圖完成的任務。或者是不是這樣? – Steven

+0

嘗試一下,它不會工作,因爲'Equals'實際上不是界面的一部分。如果你進一步將其限制爲'class'或其他東西,它就會開始工作。 – Blindy

2

我不明白爲什麼AddUnique必須使用Finddefault比較,你能不能用Count0比較?

if Count(x => x.Name == item.Name) = 0 { 
    .... 

UniqueList<T>好像有IEqualityComparer是比較T.Name

+0

好點。這簡化了事情,並且完全不需要單獨測試一個設備。另外,任何(x => x.Name == item.Name)都會給出答案作爲布爾函數的初始值,並且可能會更有效率(儘管這個屬性是唯一性的基礎,除了滿足條件的一個項目之外,Count方法將嘗試對所有滿足條件的項目進行計數,而根據我的理解,一旦找到第一個滿足的項目,Any()就會停止) – Steven

2

我建議你實現IEquatable<>並留下比較邏輯的類創建的HashSet。也請不要使用==作爲參考類型(strings),因爲它會檢查它是否是相同的對象,而不是它們是否相等。爲了迴應他人,我建議您使用Dictionary<>來檢查唯一的項目,或者只使用現有的KeyedCollection<string, INamed>,它保留索引列表以及字典。

public interface INamed : IEquatable<INamed> 
{ 
    string Name { get;} 
} 

public abstract class NamedItem : INamed 
{ 
    public abstract string Name { get; } 
    public bool Equals(INamed other) 
    { 
     if(other==null) return false;   
     return Name.Equals(other.Name); 
    } 
} 

public class UniqueList<T> : List<T> 
    where T : INamed 
{ 

    public T AddUnique(T item) 
    { 
     int index = FindIndex((x) => item.Equals(x)); 
     if (index < 0) 
     { 
      Add(item); 
      return item; 
     } 
     else 
     { 
      return this[index]; 
     } 
    } 
} 
0

您是否嘗試過在此行的某處定義您的接口?

interface INamed : IEqualityComparer<T> 
{ 
    string Name { get;} 
} 

然後在你的方法做

public T AddUnique(T item) 
{ 
    T result = Find(x => x.Name == item.Name); 
    if (result.Equals(default(T))) 
    { 
     Add(item); 
     return item; 
    } 
    return result; 
} 

注:上面的代碼沒有進行測試,可能需要調整

1

下面是使用KeyedCollection<>基類的第2個答案。

class Program 
{ 

    static void Main(string[] args) 
    { 
     UniqueList<IntValue> list = new UniqueList<IntValue>(); 

     list.Add(new IntValue("Smile", 100)); 
     list.Add(new IntValue("Frown", 101)); 
     list.Add(new IntValue("Smile", 102)); // Error, key exists already 
     int x = list["Smile"].Value; 
     string frown = list[1].Name; 
    } 
} 

public interface INamed : IEquatable<INamed> 
{ 
    string Name { get;} 
} 

public abstract class NamedItem : INamed 
{ 
    public abstract string Name { get; } 
    public bool Equals(INamed other) 
    { 
     if(other==null) return false;   
     return Name.Equals(other.Name); 
    } 
} 

public class IntValue : NamedItem 
{ 
    string name; 
    int value; 

    public IntValue(string name, int value) 
    { 
     this.name = name; 
     this.value = value; 
    } 

    public override string Name { get { return name; } } 
    public int Value { get { return value; } } 
} 

public class UniqueList<T> : KeyedCollection<string, T> 
    where T : INamed 
{ 
    protected override string GetKeyForItem(T item) 
    { 
     return item.Name; 
    } 

} 
1

我會提出一個解決方案,使用「鴨打字」。鴨子在這個實例中鍵入意味着「如果它有一個名爲的字符串屬性名稱,那麼它是可用的。」鴨子打字不依賴於接口或抽象基類。

我從KeyedCollection獲得集合類,因爲此類已提供關鍵支持,但其他類(包括List<T>)也是可能的。

class NamedItemCollection<T> : KeyedCollection<string, T> { 

    private static readonly Func<T, string> keyProvider; 

    static NamedItemCollection() { 
     var x = Expression.Parameter(typeof(T), "x"); 
     var expr = Expression.Lambda<Func<T, string>>(
      Expression.Property(x, "Name"), 
      x); 
     keyProvider = expr.Compile(); 
    } 

    protected override string GetKeyForItem(T item) { 
     return keyProvider(item); 
    } 

} 

該類首次與特定類型參數一起使用時,我們使用動態代碼生成來編譯一個小方法。該方法以類型安全的方式讀取name屬性 - 不管容器類型如何!

public abstract class NamedItem { public abstract string Name { get; } } 
struct Thing { public string Name { get; set; } } 

var namedItems1 = new NamedItemCollection<NamedItem>(); 
var namedItems2 = new NamedItemCollection<Thing>(); 
var namedItems3 = new NamedItemCollection<Type>(); 
1

的約束添加到你的T的建議是好的,因爲它代表的問題是 - 正如其他人所說 - 那你的T不保證實現==操作符。不過,它會實現Equals方法,所以你可以用它來代替......如果你不想將泛型限制在一個獨一無二的基於引用或基於值的實現中,這很方便。

在Blindy的評論後,有一個警告。雖然Equals在所有對象類型上都是合法的,但如果源對象爲null(即null.Equals(...)會引發運行時錯誤),那麼這是不合法的,並且在T是類的情況下,它的默認值將爲空,因此在嘗試調用空引用上的Equals之前,您需要考慮該情況。

這裏是我的代碼,同時使用類的結構實現:

public interface INamed 
{ 
    string Name { get; set; } 
} 

public class Foo<T> 
    : List<T> 
    where T : INamed 
{ 
    public bool IsUnique(T item) 
    { 
     T result = Find(x => x.Name == item.Name); 
     if (result == null || result.Equals(default(T))) 
      return true; 
     return false; 
    } 
} 

public class BarClass : INamed 
{ 
    public string Name { get; set; } 
} 
public struct BarStruct : INamed 
{ 
    public string Name { get; set; } 
} 
[STAThread] 
static void Main() 
{ 
    BarClass bc = new BarClass { Name = "test" }; 
    Foo<BarClass> fc = new Foo<BarClass>(); 
    fc.IsUnique(bc); 

    BarStruct bs = new BarStruct { Name = "test" }; 
    Foo<BarStruct> fs = new Foo<BarStruct>(); 
    fs.IsUnique(bs); 
}