2011-09-20 33 views
5

Tạo danh sách đơn giản (UniqueList) của các mục có thuộc tính Tên chỉ được chứa các mục duy nhất (được định nghĩa là có các tên khác nhau). Ràng buộc vào loại UniqueList có thể là một giao diện:Ràng buộc về thông số loại: giao diện so với lớp trừu tượng

interface INamed 
{ 
    string Name { get;} 
} 

hoặc một lớp trừu tượng:

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

Vì vậy, các UniqueList có thể là:

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

hoặc

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

Hàm lớp, AddUni que:

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

Nếu kiểu lớp hạn chế được dựa trên giao diện, kết quả biên soạn trong

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

tại dòng

if (result == default(T)) 

Tất cả cũng là nếu tôi căn UniqueList trên trừu tượng lớp học. Có suy nghĩ gì không?

+1

Tại sao bạn không sử dụng một 'HashSet ' cho việc này? Có chính xác những gì bạn muốn – BrokenGlass

+0

Nếu T là một loại giá trị, sẽ Tìm trả về 'mặc định (T)' khi không có gì có thể được tìm thấy? Điều này có nghĩa là 'mặc định (T)' không được phép làm giá trị, có thể không phải là ý định của bạn. –

+1

@BrokenGlass Nhưng anh ấy không tạo ra một tập hợp các chuỗi, anh ấy đang tạo ra một tập hợp các thứ được đặt tên. Anh ta thực sự muốn một 'HashSet ' với một 'IEqualityComparer ' – phoog

Trả lời

5

Đó là vì giao diện có thể được áp dụng cho cấu trúc là loại giá trị. Để làm cho nó làm việc với giao diện mở rộng các hạn chế như thế này:

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

Điều đó sẽ đảm bảo bạn sẽ không thể vượt qua một cấu trúc như T và do đó default(T) sẽ đánh giá để null đó là những gì bạn mong đợi.


Ngoài ra, tôi muốn khuyên bạn nên khái quát hóa UniqueList của bạn một chút, cho phép loại khác nhau của các phím duy nhất:

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

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

Sau đó giao diện INamed có thể dễ dàng khai báo là:

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

UniqueKey hoặc Name sẽ được chèn một cách rõ ràng trong lớp triển khai để ngăn không cần thiết (trùng lặp trong thực tế) các thành viên lớp công khai.

1

Có. Sử dụng lớp trừu tượng cung cấp cho bạn việc triển khai System.Object của toán tử ==, trong khi sử dụng giao diện không bảo đảm cho trình biên dịch rằng kiểu sẽ hỗ trợ toán tử ==, vì nó có thể là một cấu trúc không thực hiện. Nếu bạn thêm ràng buộc class vào thông số loại của mình, nó sẽ biên dịch.

1

Giao diện không cung cấp triển khai cho Equals, vì vậy bạn không thể so sánh các mục.

Nhân tiện, bạn dường như đang thực hiện lại HashSet, hãy cân nhắc sử dụng lớp học được cung cấp chính thức thay thế.

+0

Đừng quên rằng phương thức 'Equals' không giống với toán tử' == ' – phoog

+0

re: Giao diện không cung cấp một thực hiện cho Equals; Tôi không chắc chắn về từ ngữ của bạn ở đây. Có vẻ như nhà điều hành không được đảm bảo tồn tại, nhưng phương pháp Equals sẽ hoạt động, phải không? Tôi sẽ nghĩ rằng thay thế "kết quả == mặc định (T)" với "result.Equals (mặc định (T))" sẽ thực hiện những gì Ol 'Badger đang cố gắng để thực hiện. Hay là không phải vậy? – Steven

+0

Hãy thử nó, nó sẽ không hoạt động vì 'Equals' là nghĩa đen không phải là một phần của giao diện. Nếu bạn tiếp tục giới hạn nó thành 'class' hoặc cái gì đó, nó sẽ bắt đầu hoạt động. – Blindy

2

Tôi không hiểu tại sao AddUnique phải sử dụng Find và so sánh với default, bạn có thể không sử dụng Count và so sánh với 0?

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

UniqueList<T> có vẻ như một HashSet tạo ra với một IEqualityComparer so sánh T.Name

+0

Điểm tốt. Điều đó đơn giản hóa mọi thứ và loại bỏ sự cần thiết cho một thử nghiệm riêng biệt của eqality hoàn toàn. Ngoài ra, bất kỳ (x => x.Name == item.Name) sẽ cung cấp cho câu trả lời như là một boolean trả trước, và có thể * có thể * được hiệu quả hơn (mặc dù, vì tài sản này là cơ sở cho tính độc đáo, không bao giờ nên được nhiều hơn hơn một mục thỏa mãn điều kiện, phương thức Count sẽ cố gắng đếm tất cả các mục thỏa mãn nó, trong khi, từ sự hiểu biết của tôi, Any() sẽ dừng lại khi tìm thấy mục thỏa mãn đầu tiên) – Steven

2

tôi đề nghị bạn thực hiện IEquatable<> và rời khỏi logic so với lớp. Cũng không sử dụng == cho các loại tham chiếu (strings) vì nó kiểm tra nếu nó là cùng một đối tượng, không nếu chúng bằng nhau. Để lặp lại những người khác, tôi khuyên bạn nên sử dụng Dictionary<> để kiểm tra các mục duy nhất hoặc chỉ sử dụng KeyedCollection<string, INamed> hiện có để giữ danh sách được lập chỉ mục cũng như từ điển.

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

Bạn đã thử xác định giao diện của mình ở đâu đó dọc theo dòng này chưa?

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

Và sau đó trong phương pháp của bạn làm

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

Chú ý: mã trên không được thử nghiệm, có thể cần phải tinh chỉnh

1

Đây là một câu trả lời thứ 2 bằng cách sử dụng lớp KeyedCollection<> cơ sở.

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

Tôi sẽ đề xuất giải pháp sử dụng "nhập vịt". Việc nhập Duck trong trường hợp này có nghĩa là "Nếu nó có thuộc tính chuỗi được gọi là Tên thì có thể sử dụng được". Việc gõ vịt không phụ thuộc vào các giao diện hoặc các lớp cơ sở trừu tượng.

Tôi đang phát sinh lớp thu thập từ KeyedCollection vì lớp này đã cung cấp hỗ trợ chính, nhưng các lớp khác, bao gồm List<T> là có thể.

Lần đầu tiên lớp được sử dụng với thông số loại cụ thể, chúng tôi sử dụng tạo mã động để biên dịch một phương thức nhỏ. Phương thức này đọc thuộc tính name theo cách an toàn - không quan trọng loại container!

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

Những gợi ý thêm một rào cản đối với T của bạn là những người tốt, bởi vì vấn đề như nó đứng là - như những người khác đã đề cập đến - đó T của bạn không được bảo đảm để thực hiện các toán tử ==. Tuy nhiên, nó sẽ thực hiện phương thức Equals, vì vậy bạn có thể sử dụng nó ... điều này rất tiện lợi nếu bạn không muốn nhất thiết phải tạo ra các generics của bạn cho một triển khai dựa trên tham chiếu hoặc dựa trên giá trị.

Sau khi nhận xét của Blindy, có một thông báo trước. Trong khi Equals là hợp pháp trên tất cả các loại đối tượng, nó không hợp lệ nếu đối tượng nguồn của bạn là một null (tức là null.Equals (...) sẽ ném một lỗi thời gian chạy), và trong trường hợp T là một lớp, mặc định của nó sẽ là null, vì vậy bạn sẽ cần phải tính toán cho tình huống đó trước khi cố gắng gọi Equals trên một tham chiếu null.

Dưới đây là mã của tôi, trong đó sử dụng cả lớp một thực hiện cấu trúc:

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); 
} 
Các vấn đề liên quan