2012-07-19 34 views
20

Có cách nào để có phương thức trả về bất kỳ một trong số một số kiểu generic từ một phương thức không? Ví dụ, tôi có những điều sau đây:Loại trả về chung có thể thay đổi trong C#

public static T ParseAttributeValue<T>(this XElement element, string attribute) 
    { 
     if(typeof(T) == typeof(Int32)) 
     { 
      return Int32.Parse(element.Attribute(attribute).Value); 
     } 

     if(typeof(T) == typeof(Double)) 
     { 
      return Double.Parse(element.Attribute(attribute).Value); 
     } 

     if(typeof(T) == typeof(String)) 
     { 
      return element.Attribute(attribute).Value; 
     } 

     if(typeof(T) == typeof(ItemLookupType)) 
     { 
      return Enum.Parse(typeof(T), element.Attribute(attribute).Value); 
     } 
    } 

(Đây chỉ là một mockup rất nhanh chóng, tôi nhận thức được rằng bất kỳ mã sản xuất sẽ cần phải được đáng kể kỹ hơn trong kiểm tra rỗng vv ...)

Nhưng trình biên dịch không thích nó, phàn nàn rằng Int32 không được chuyển đổi hoàn toàn thành T (nó cũng không hoạt động với dàn diễn viên). Tôi có thể hiểu điều đó. Tại thời gian biên dịch nó không có cách nào để biết những gì T là, nhưng tôi đang kiểm tra nó trước. Có anyway tôi có thể thực hiện công việc này?

+0

không, '' chỉ là thông số loại. Bạn sử dụng loại không có dấu ngoặc vuông. – Femaref

+0

@Femaref - Điểm tốt. Đã rút câu hỏi. –

+0

Nếu bạn lưu trữ giá trị dưới dạng Đối tượng, bạn có thể truyền giá trị đó. Cũng có thể việc nhập hàm là động có thể giúp bạn làm như vậy mà không cần một diễn viên. – MrWednesday

Trả lời

20

Tôi đã thực hiện các loại phương pháp chung này trong quá khứ. Cách dễ nhất để nhận suy luận kiểu là cung cấp chức năng chuyển đổi chung.

public static T ParseAttributeValue<T> 
      (this XElement element, string attribute, Func<string, T> converter) 
{ 
    string value = element.Attribute(attribute).Value; 
    if (String.IsNullOrWhiteSpace(value)) { 
    return default(T); 
    } 

    return converter(value); 
} 

Bạn có thể sử dụng nó như sau:

int index = element.ParseAttributeValue("index", Convert.ToInt32); 
double price = element.ParseAttributeValue("price", Convert.ToDouble); 

Bạn thậm chí có thể cung cấp các chức năng của riêng bạn và có tất cả những niềm vui trên thế giới (thậm chí trở lại các loại vô danh):

ItemLookupType lookupType = element.ParseAttributeValue("lookupType", 
    value => Enum.Parse(typeof(ItemLookupType), value)); 

var item = element.ParseAttributeValue("items", 
    value => { 
    List<string> items = new List<string>(); 
    items.AddRange(value.Split(new [] { ',' })); 
    return items; 
    }); 
+0

Tôi không nghĩ nó có thể tao nhã hơn thế. +1. – Adam

+0

Điều tốt về điều này là 'T' có thể được suy ra từ việc sử dụng vì nó nằm trong' Func <,> '. Vì vậy, ngay cả khi chúng ta phải cung cấp một tham số phụ (cụ thể là 'Func <,>' delegate), chúng ta sẽ không phải chỉ định tham số kiểu 'T' một cách rõ ràng. –

+1

Rất nhiều bộ chuyển đổi chung đã tồn tại trong khung .Net: 'TypeDescriptor.GetTypeConverter (typeof (T));' –

4

Tại sao bạn sử dụng tham số kiểu làm kiểu trả về? Điều này sẽ làm việc, chỉ đòi hỏi một diễn viên sau khi gọi:

public static Object ParseAttributeValue<T>(this XElement element, string attribute) 
{ 
    if(typeof(T) == typeof(Int32)) 
    { 
     return Int32.Parse(element.Attribute(attribute).Value); 
    } 

    if(typeof(T) == typeof(Double)) 
    { 
     return Double.Parse(element.Attribute(attribute).Value); 
    } 

    if(typeof(T) == typeof(String)) 
    { 
     return element.Attribute(attribute).Value; 
    } 

    if(typeof(T) == typeof(ItemLookupType)) 
    { 
     return Enum.Parse(typeof(T), element.Attribute(attribute).Value); 
    } 
} 

Hoặc tốt hơn:

public static Int32 ParseAsInt32(this XElement element, string attribute) 
{ 
    return Int32.Parse(element.Attribute(attribute).Value); 
} 

// etc, repeat for each type 

tiếp cận thứ hai này có lợi ích bổ sung của việc có một khả năng cao hơn nhiều của việc sắp xếp theo hàng cộng với nó sẽ (đối với các loại giá trị như Int32), hãy ngăn không cho hộp/bỏ hộp giá trị. Cả hai điều này sẽ khiến phương thức thực hiện nhanh hơn một chút.

+0

+1 cho đề xuất thứ hai của bạn. Không có lý do gì để sử dụng sự phản chiếu để tìm ra kiểu và thực hiện hành động tương ứng khi ngôn ngữ đã cung cấp các phương thức quá tải, một cơ sở thực hiện công việc này cho bạn (và có lẽ hiệu quả hơn). –

+0

Có, từ bỏ generics và làm cho phương pháp rõ ràng được đặt tên là một giải pháp phổ biến cho điều này, và không phải là xấu. Nhập 'ParseAttributeValueAsString' gần như dễ dàng như' ParseAttributeValue ', có hoặc không có intellisense. Và phương pháp chung là một loạt các trường hợp đặc biệt đã có, vì vậy không có trùng lặp xảy ra nếu bạn kéo nó ra phương pháp riêng biệt. –

2

Không chắc chắn nếu điều này là chính xác những gì bạn muốn, nhưng bạn có thể làm cho lợi nhuận làm việc nếu bạn đúc để object đầu tiên sau đó đến T

public static T ParseAttributeValue<T>(this XElement element, string attribute) 
    { 
     if (typeof(T) == typeof(Int32)) 
     { 
      return (T)(object)Int32.Parse(element.Attribute(attribute).Value); 
     } 

     if (typeof(T) == typeof(Double)) 
     { 
      return (T)(object)Double.Parse(element.Attribute(attribute).Value); 
     } 

     if (typeof(T) == typeof(String)) 
     { 
      return (T)(object)element.Attribute(attribute).Value; 
     } 

     return default(T); 
    } 

Tuy nhiên bạn vẫn phải cung cấp T tại thời gian biên dịch, gọi phương thức như:

int value = element.ParseAttributeValue<int>("attribute"); 
+0

Đó là giải pháp ngay lập tức. Nó luôn được phép đưa một tham số kiểu vào 'object' một cách rõ ràng, và sau đó' đối tượng' có thể được truyền tới bất cứ thứ gì.Nhưng nó _is_ xấu xí. Và nó liên quan đến boxing và unboxing trong trường hợp của 'Int32',' Double', và các cấu trúc khác. –

2

Dưới đây là hai cách để làm điều đó ...

static T ReadSetting<T>(string value) 
    { 
     object valueObj = null; 
     if (typeof(T) == typeof(Int32)) 
      valueObj = Int32.Parse(value); 
     return (T)valueObj; 
    } 
    static dynamic ReadSetting2<T>(string value) 
    { 
     if (typeof(T) == typeof(Int32)) 
      return Int32.Parse(value); 
     throw new UnsupportedException("Type is unsupported"); 
    } 
    static void Main(string[] args) 
    { 
     int val1 = ReadSetting<Int32>("2"); 
     int val2 = ReadSetting2<Int32>("3"); 
    } 
+1

'ReadSetting' sẽ ném một' NullReferenceException' nếu kiểu generic là một kiểu giá trị, vì các kiểu giá trị không thể là null. Đó là lý do tại sao nó nguy hiểm để hộp đến một 'đối tượng'. – Joshua

+0

Điều đó đúng, nhưng nó có thể là một kết quả thích hợp trong nhiều trường hợp. Ví dụ, giả vờ rằng đây không chỉ là mã mẫu đơn giản được viết để hiển thị các phương pháp, người gọi có thể mong đợi một ngoại lệ hợp lý nếu họ đang cố đọc một cài đặt không thể phân tích cú pháp hoặc không được chỉ định. – MrWednesday

1

Với C++ template, đây loại điều sẽ làm việc, nhưng chỉ khi mỗi đoạn mã trong một chuyên môn khác nhau, riêng biệt. Điều làm cho công việc đó là các mẫu chức năng không được sử dụng không được biên dịch (hoặc chính xác hơn: không được khởi tạo đầy đủ), vì vậy thực tế là một đoạn mã sẽ không hợp lệ nếu bản sao của mẫu đó được khởi tạo với một kiểu khác không đi lên.

C# khác nhau và AFAIK không có chuyên môn cho generics. Một cách để thực hiện những gì bạn đang cố gắng làm, trong khi làm việc trong các giới hạn của C# sẽ là tạo một hàm với kiểu trả về trừu tượng hơn và chỉ sử dụng ParseAttributeValue để đúc nó thành T.

Vì vậy, bạn sẽ có:

private static Object AbstractParseValue(System.Type t, XElement element, string attribute) 

public static T ParseAttributeValue<T>(this XElement element, string attribute) 
{ 
    return (T)AbstractParseValue(typeof(T), element, attribute); 
} 
8

Net đã có một loạt các thói quen chuỗi chuyển đổi tuyệt vời mà bạn có thể sử dụng! A TypeConverter có thể thực hiện hầu hết việc nâng hạng nặng cho bạn. Sau đó, bạn không phải lo lắng về việc cung cấp các triển khai phân tích cú pháp của riêng mình cho các kiểu dựng sẵn.

Lưu ý rằng có các phiên bản nhận dạng miền địa phương của API trên TypeConverter có thể được sử dụng nếu bạn cần xử lý các giá trị phân tích được thể hiện trong các nền văn hóa khác nhau.

Đoạn mã dưới đây sẽ phân tích giá trị bằng cách sử dụng văn hóa mặc định:

using System.ComponentModel; 

public static T ParseAttributeValue<T>(this XElement element, string attribute) 
{ 
    var converter = TypeDescriptor.GetConverter(typeof(T)); 
    if (converter.CanConvertFrom(typeof(string))) 
    { 
     string value = element.Attribute(attribute).Value; 
     return (T)converter.ConvertFromString(value); 
    } 

    return default(T); 
} 

này sẽ làm việc cho rất nhiều loại custom built-in các loại, và bạn có thể trang trí với một TypeConverterAttribute để cho phép họ tham gia vào loại trò chơi chuyển đổi quá. Điều này có nghĩa là trong tương lai bạn sẽ có thể phân tích các loại mới mà không phải thay đổi việc triển khai ParseAttributeValue.

xem: http://msdn.microsoft.com/en-us/library/system.componentmodel.typeconverter.aspx

0

tôi sẽ đề nghị rằng thay vì kiểm tra các tham số kiểu mỗi khi thường xuyên được thực hiện, bạn nên tạo một lớp tĩnh một cái gì đó chung chung như thế này:

 
internal static class ElementParser<T> 
{ 
    public static Func<XElement, string, T> Convert = InitConvert; 

    T DefaultConvert(XElement element, string attribute) 
    { 
    return Default(T); // Or maybe throw exception, or whatever 
    } 

    T InitConvert(XElement element, string attribute) 
    { 
    if (ElementParser<int>.Convert == ElementParser<int>.InitConvert) 
    { // First time here for any type at all 
     Convert = DefaultConvert; // May overwrite this assignment below 
     ElementParser<int>.Convert = 
     (XElement element, string attribute) => 
      Int32.Parse(element.Attribute(attribute).Value); 
     ElementParser<double>.Convert = 
     (XElement element, string attribute) => 
      Int32.Parse(element.Attribute(attribute).Value); 
     // etc. for other types 
    } 
    else // We've done other types, but not this type, and we don't do anything nice for it 
    { 
     Convert = DefaultConvert; 
    } 
    return Convert(element, attribute);  
    } 
} 
public static T ParseAttributeValue(this XElement element, string attribute) 
{ 
    ElementParser<T>.Convert(element, attribute); 
} 

Sử dụng phương pháp này, người ta sẽ chỉ phải xử lý đặc biệt lần đầu tiên một loại cụ thể được sử dụng. Sau đó, chuyển đổi có thể được thực hiện bằng cách sử dụng chỉ một lời gọi đại biểu chung duy nhất. Một khi có thể dễ dàng thêm bất kỳ số lượng các loại, và thậm chí cho phép chuyển đổi được đăng ký cho bất kỳ loại mong muốn tại thời gian chạy.

+0

Tại sao không chỉ sử dụng được xây dựng trong lớp 'TypeConverter'? –

+0

Nếu lớp học cụ thể đó sẽ hoạt động đầy đủ cho ứng dụng của một người, tuyệt vời. Nếu ai cần bất kỳ hành vi cụ thể nào mà lớp đó không hỗ trợ, cách tiếp cận được cung cấp ở trên có thể dễ dàng chứa bất kỳ bản dịch bổ sung mong muốn nào. – supercat

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