2011-09-20 14 views
5

Nameプロパティを持つ項目の単純なリスト(UniqueList)には、一意の項目(名前が異なると定義されている)のみが含まれていなければなりません。タイプパラメータの制約:インターフェイスと抽象クラス

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

だからUniqueListができ:

interface INamed 
{ 
    string Name { get;} 
} 

または抽象クラス:

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

または

class UniqueList<T> : List<T> where T : NamedItem 
UniqueListタイプの制約は、インターフェースすることができ

クラス関数AddUni QUE:ラインで

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

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

クラス型制約は、インターフェイスに基づいている場合、コンパイル結果

if (result == default(T)) 

すべては、私は抽象的にUniqueListをベース場合もありますクラス。何かご意見は?

+1

は、なぜあなたは、このために 'HashSetの'を使用していない:

はここ構造の実装の両方クラスを使用しています私のコードを、ですか? – BrokenGlass

+0

Tが値型の場合、何も見つからない場合に 'default(T)'を返しますか?これは 'default(T)'があなたの意図ではないかもしれない値として許可されていないことを意味します。 –

+1

@BrokenGlassしかし、彼は一連の文字列を作成していないので、名前付きのものを作成しています。彼は本当に 'IEqualityComparer 'で' HashSet を望んでいる。 – 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;} 
} 

UniqueKeyまたはNameは、実装クラス内で明示的にimplemetedされ、不要な(実際は)パブリッククラスメンバー。

1

はい。抽象クラスを使用すると、==演算子のSystem.Object実装が提供されますが、インタフェースを使用すると、実装がない構造体である可能性があるため、型が==演算子をサポートすることがコンパイラに保証されません。型パラメータにclassという制約を追加すると、コンパイルする必要があります。

1

インターフェイスはEqualsの実装を提供しないため、アイテムを比較することはできません。

ところで、あなたはHashSetを再実装しているようですが、代わりに公式提供クラスの使用を検討してください。

+0

'Equals'メソッドが' == '演算子と同じでないことを忘れないでください。 – phoog

+0

re:インターフェイスは等しいの実装。私はあなたの言葉遣いについてここでは分かりません。演算子が存在することは保証されていないようですが、Equalsメソッドはうまくいくはずですか?私は、 "result == default(T)"を "result.Equals(default(T))"に置き換えると、Ol 'Badgerが達成しようとしていることを達成すると思います。それともそれじゃないの? – Steven

+0

'Equals'が文字通りインターフェースの一部ではないので、試してみてください。さらにそれを 'class'などに制限すると、それが動作し始めます。 – Blindy

2

AddUniqueあなたはCountを使用して0と比較することはできません、Findを使用してdefaultと比較しなければならない理由を私は理解していませんか?

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

UniqueList<T>私はあなたがIEquatable<>を実装したクラスに比較ロジックを残す示唆T.Name

+0

良い点。これは物事を単純化し、eqalityの別個のテストの必要性を排除します。また、Any(x => x.Name == item.Name)は、ブール値の先頭に答えを与えるでしょうし、*もっと効率的かもしれません(ただし、このプロパティは一意性の基礎であるため、条件を満たしている1つのアイテムよりCountメソッドは、それを満足するすべてのアイテムをカウントしようとしますが、私の理解から、最初の満足するアイテムが見つかるとAny()は停止します) – Steven

2

を比較IEqualityComparerで作成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<>基本クラスを使用して第二の答えです。

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プロパティを読み取ります。他の人が言及したように - - あなたのTは、==演算子を実装するために保証されていないことを、そのままで問題があるので、あなたのTに制約を追加する

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

提案は、良いものです。ただし、Equalsメソッドを実装するので、代わりに使用することができます。これは、ジェネリックを排他的参照または値ベースの実装に制限したくない場合に便利です。

Blindyのコメント後、警告があります。 Equalsはすべてのオブジェクト型で正当ですが、ソースオブジェクトがnullの場合(つまり、null.Equals(...)は実行時エラーをスローします)、Tがクラスの場合はデフォルトnullであるため、null参照で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); 
} 
関連する問題