2012-07-05 30 views
15

Tôi tự hỏi liệu có cách tiếp cận nào để thực hiện mẫu đối tượng null chung trong C# không. Đối tượng null chung là lớp con của tất cả các kiểu tham chiếu, giống như Nothing trong Scala. Nó có vẻ nhưMẫu đối tượng null chung trong C#

public class Nothing<T> : T where T : class 

Nhưng nó không thể biên dịch và tôi đã không có ý tưởng thế nào để thực hiện các phương pháp T để cung cấp hành vi mặc định hoặc ném một ngoại lệ. Dưới đây là một số suy nghĩ:

  1. Sử dụng phản ánh?
  2. Sử dụng cây biểu thức khi tạo Nothing<T>? Nó có thể trông giống như Moq. Và một câu hỏi khác xuất hiện: Có thể sử dụng khung/thư viện giả trong mã sản phẩm không?
  3. Sử dụng các loại động?

Tôi BIẾT có lẽ tôi nên triển khai đối tượng null cụ thể cho loại cụ thể. Tôi chỉ tò mò muốn biết liệu có giải pháp nào không.

Bất kỳ đề xuất nào? Cảm ơn.

+0

Điều này có thể hữu ích: http://stackoverflow.com/questions/ 2522928/how-can-i-implement-the-null-object-design-pattern-in-a-generic-form – Maarten

Trả lời

10

Với Generics, bạn không thể xác định kế thừa từ T. Nếu ý định của bạn là sử dụng if(x is Nothing<Foo>), thì điều đó sẽ không hoạt động. Không kém phần quan trọng, bạn cần phải suy nghĩ về các kiểu trừu tượng, các kiểu niêm phong và các hàm tạo không mặc định. Tuy nhiên, bạn có thể thực hiện một số việc như:

public class Nothing<T> where T : class, new() 
{ 
    public static readonly T Instance = new T(); 
} 

Tuy nhiên, IMO không thực hiện được hầu hết các tính năng chính của đối tượng rỗng; đặc biệt, bạn có thể dễ dàng kết thúc với một người nào đó thực hiện:

Nothing<Foo>.Instance.SomeProp = "abc"; 

(có lẽ vô tình, sau khi đi qua một đối tượng 3 cấp xuống)

Thẳng thắn mà nói, tôi nghĩ rằng bạn chỉ nên kiểm tra null.

+0

Cảm ơn. Tôi không định sử dụng 'if (x là Nothing

+1

@KirinYao bạn sẽ phải làm điều đó trên cơ sở mỗi loại, hoặc sử dụng các phương pháp mở rộng (sau đó có các vấn đề riêng biệt với đa hình) –

+0

Còn giải pháp thứ 2 tôi đã đề cập? Tôi có thể sử dụng cây biểu thức để mô phỏng hành vi như Moq không? Đó có phải là tốt để sử dụng khuôn khổ giả trong mã sản phẩm? –

8

Làm thế nào về điều này?

public class Nothing<T> where T : class 
{ 
    public static implicit operator T(Nothing<T> nothing) 
    { 
      // your logic here 
    } 
} 
+0

Và sau đó làm thế nào tôi có thể cung cấp hành vi mặc định? –

+0

@KirinYao: Vui lòng mô tả hành vi mặc định nào bạn mong đợi? – abatishchev

+0

Nó phụ thuộc vào 'T' khác nhau. Vì vậy, tôi liệt kê 3 cách tiếp cận có thể có. Đơn giản, tôi chỉ có thể ném ngoại lệ khi mã máy khách gọi các thành viên của 'T'. –

0

Làm thế nào về việc triển khai Nullable hiện có trong Khuôn khổ .NET? http://msdn.microsoft.com/en-us/library/b3h38hb0.aspx

+4

'Nullable ' có nghĩa là một loại-mà-không-có-null bây giờ có thể được. OP hỏi về một điều khác. – abatishchev

+4

Nó cũng liên quan đến * giá trị *, không phải * đối tượng * –

+0

Đúng, xấu của tôi. bằng cách sử dụng Generics, ông sẽ có thể làm cho phương pháp mặc dù, tôi không chắc chắn những gì OP thực sự muốn thực hiện với đối tượng "cơ sở" này. – thmsn

1

Vì có các lớp được niêm phong, bạn không thể thực hiện kế thừa đó trong trường hợp chung. Nhiều lớp học không được mong đợi có nguồn gốc từ, do đó, nó có thể không được ý tưởng tốt nếu nó sẽ làm việc.

Sử dụng toán tử ngầm như được gợi ý bởi @abatishchev giống như cách tiếp cận có thể có.

1

tôi sử dụng một cái gì đó như thế này trong các dự án của tôi:

public interface IOptional<T> : IEnumerable<T> { } 
public interface IMandatory<T> : IEnumerable<T> { } 

Hai giao diện bắt nguồn từ IEnumerable để tương thích với LINQ

public class Some<T> : IOptional<T> 
{ 
    private readonly IEnumerable<T> _element; 
    public Some(T element) 
     : this(new T[1] { element }) 
    { 

    } 
    public Some() 
     : this(new T[0]) 
    {} 
    private Some(T[] element) 
    { 
     _element = element; 
    } 
    public IEnumerator<T> GetEnumerator() 
    { 
     return _element.GetEnumerator(); 
    } 
    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 
} 

public class Just<T> : IMandatory<T> 
{ 
    private readonly T _element; 

    public Just(T element) 
    { 
     _element = element; 
    } 
    public IEnumerator<T> GetEnumerator() 
    { 
     yield return _element; 
    } 
    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 
} 

Thực hiện các lớp học Chỉ và một số báo

: Việc thực hiện các lớp này rất giống nhau, nhưng nó có một sự khác biệt. Lớp Chỉ xuất phát từ giao diện IMandatory và chỉ có một hàm tạo, đảm bảo rằng cá thể của lớp Chỉ cần luôn có một giá trị bên trong.

public static class LinqExtensions 
{ 
    public static IMandatory<TOutput> Match<TInput, TOutput>(
     this IEnumerable<TInput> maybe, 
     Func<TInput, TOutput> some, Func<TOutput> nothing) 
    { 
     if (maybe.Any()) 
     { 
      return new Just<TOutput>(
         some(
          maybe.First() 
         ) 
        ); 
     } 
     else 
     { 
      return new Just<TOutput>(
         nothing() 
        ); 
     } 
    } 
    public static T Fold<T>(this IMandatory<T> maybe) 
    { 
     return maybe.First(); 
    } 
} 

Một số phần mở rộng cho thực tiễn

Chú ý: Phương pháp mở rộng trận đấu đòi hỏi hai chức năng và trở IMandatory, sau này, phương pháp khuyến nông Fold sử dụng .First() mà không cần bất kỳ kiểm tra.

Bây giờ chúng ta có thể sử dụng đầy đủ sức mạnh của LINQ và viết mã tương tự này (Ý tôi là monads .SelectMany())

var five = new Just<int>(5); 
var @null = new Some<int>(); 

Console.WriteLine(
      five 
       .SelectMany(f => @null.Select(n => f * n)) 
       .Match(
        some: r => $"Result: {r}", 
        nothing:() => "Ups" 
       ) 
       .Fold() 
     ); 
+0

Tôi lấy ý tưởng này từ Zoran Horvat [post] (http://www.codinghelmet.com/?path=howto/reduce-cyclomatic-complexity-option-functional-type) – kogoia

Các vấn đề liên quan