C # Generics và Type Kiểm tra


82

Tôi có một phương thức sử dụng một IList<T>làm tham số. Tôi cần kiểm tra loại Tđối tượng đó là gì và làm gì đó dựa trên nó. Tôi đã cố gắng sử dụngT giá trị, nhưng trình biên dịch không cho phép nó. Giải pháp của tôi là như sau:

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        if (clause[0] is int || clause[0] is decimal)
        {
           //do something
        }
        else if (clause[0] is String)
        {
           //do something else
        }
        else if (...) //etc for all the types
        else
        {
           throw new ApplicationException("Invalid type");
        }
    } 
}

Phải có một cách tốt hơn để làm điều này. Có cách nào để tôi có thể kiểm tra loại Tđược truyền vào và sau đó sử dụng một switchcâu lệnh không?


1
Cá nhân tôi muốn biết bạn đang làm gì đặc biệt cho từng loại dữ liệu. Nếu bạn đang thực hiện chuyển đổi gần như giống nhau cho từng kiểu dữ liệu, thì việc ánh xạ các kiểu khác nhau vào một giao diện chung và hoạt động trên giao diện đó có thể dễ dàng hơn.
Juliet

Câu trả lời:


120

Bạn có thể sử dụng quá tải:

public static string BuildClause(List<string> l){...}

public static string BuildClause(List<int> l){...}

public static string BuildClause<T>(List<T> l){...}

Hoặc bạn có thể kiểm tra loại thông số chung:

Type listType = typeof(T);
if(listType == typeof(int)){...}

23
+1: quá tải chắc chắn là giải pháp tốt nhất ở đây về mặt thiết kế và khả năng bảo trì lâu dài. Kiểm tra kiểu thời gian chạy của một tham số chung có vẻ quá mỉa mai để viết mã bằng một mặt thẳng.
Juliet

Một ví dụ tuyệt vời về thời điểm điều này sẽ hữu ích là tuần tự hóa chung chung với các kiểu khác nhau. Nếu đối tượng được truyền vào là một chuỗi, tại sao phần bổ sung lại hoạt động? Nếu đó là một chuỗi, chỉ cần trả về chuỗi gốc mà không cố gắng bất kỳ chế biến thêm
watkinsmatthewp

Xin lỗi, có cách nào để đạt được điều đó bằng cách sử dụng switch-casethay vì if-elsekhông?
Tân

@HappyCoding rất tiếc là không = (bạn có thể làm như vậy với phiên bản tiếp theo của C #.
jonnii

7
Bạn KHÔNG nên dựa vào quá tải chung chung (xem câu trả lời này ) nếu quá tải của bạn khác nhau về chức năng (xem xét các tác dụng phụ), bởi vì bạn không thể đảm bảo quá tải chuyên biệt hơn sẽ được gọi. Quy tắc ở đây là: nếu bạn PHẢI làm logic chuyên biệt cho một loại cụ thể thì bạn phải kiểm tra loại đó và không sử dụng quá tải; tuy nhiên, nếu bạn chỉ ƯU ĐÃI làm logic chuyên biệt (tức là để cải thiện hiệu suất) nhưng tất cả các quá tải bao gồm cả trường hợp chung đều dẫn đến cùng một kết quả thì bạn có thể sử dụng quá tải thay vì kiểm tra kiểu.
tomosius

23

Bạn có thể sử dụng typeof(T).

private static string BuildClause<T>(IList<T> clause)
{
     Type itemType = typeof(T);
     if(itemType == typeof(int) || itemType == typeof(decimal))
    ...
}

7

Theo mặc định biết không có một cách tuyệt vời. Trước đây, tôi đã rất thất vọng với điều này và đã viết một lớp tiện ích nhỏ giúp ích một chút và làm cho cú pháp gọn gàng hơn một chút. Về cơ bản, nó biến mã thành

TypeSwitcher.Do(clause[0],
  TypeSwitch.Case<int>(x => ...),  // x is an int
  TypeSwitch.Case<decimal>(d => ...), // d is a decimal 
  TypeSwitch.Case<string>(s => ...)); // s is a string

Bài đăng trên blog đầy đủ và chi tiết về việc triển khai có sẵn tại đây


6

Và, bởi vì C # đã phát triển, bạn có thể (bây giờ) sử dụng đối sánh mẫu .

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        switch (clause[0])
        {
            case int x: // do something with x, which is an int here...
            case decimal x: // do something with x, which is a decimal here...
            case string x: // do something with x, which is a string here...
            ...
            default: throw new ApplicationException("Invalid type");
        }
    }
}

Và một lần nữa với các biểu thức chuyển đổi trong C # 8.0, cú pháp thậm chí còn ngắn gọn hơn.

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        return clause[0] switch
        {
            int x => "some string related to this int",
            decimal x => "some string related to this decimal",
            string x => x,
            ...,
            _ => throw new ApplicationException("Invalid type")
        }
    }
}

4

Toán tử typeof ...

typeof(T)

... sẽ không hoạt động với câu lệnh switch c #. Nhưng làm thế nào về điều này? Bài đăng sau chứa một lớp tĩnh ...

Có cách nào tốt hơn cách này để 'bật loại' không?

... điều đó sẽ cho phép bạn viết mã như thế này:

TypeSwitch.Do(
    sender,
    TypeSwitch.Case<Button>(() => textBox1.Text = "Hit a Button"),
    TypeSwitch.Case<CheckBox>(x => textBox1.Text = "Checkbox is " + x.Checked),
    TypeSwitch.Default(() => textBox1.Text = "Not sure what is hovered over"));

Cũng xem câu trả lời của JaredPar tại đây.
Robert Harvey

3

Đối với tất cả mọi người nói rằng kiểm tra các loại và thực hiện một cái gì đó dựa trên loại không phải là một ý tưởng tuyệt vời cho các loại thuốc chung, tôi đồng ý nhưng tôi nghĩ có thể có một số trường hợp mà điều này hoàn toàn hợp lý.

Ví dụ: nếu bạn có một lớp nói rằng được triển khai như vậy (Lưu ý: Tôi không hiển thị mọi thứ mà mã này làm vì đơn giản và chỉ đơn giản là cắt và dán vào đây nên nó có thể không xây dựng hoặc hoạt động như dự định giống như toàn bộ mã nhưng nó đạt được điểm qua. Ngoài ra, Đơn vị là một enum):

public class FoodCount<TValue> : BaseFoodCount
{
    public TValue Value { get; set; }

    public override string ToString()
    {
        if (Value is decimal)
        {
            // Code not cleaned up yet
            // Some code and values defined in base class

            mstrValue = Value.ToString();
            decimal mdecValue;
            decimal.TryParse(mstrValue, out mdecValue);

            mstrValue = decimal.Round(mdecValue).ToString();

            mstrValue = mstrValue + mstrUnitOfMeasurement;
            return mstrValue;
        }
        else
        {
            // Simply return a string
            string str = Value.ToString() + mstrUnitOfMeasurement;
            return str;
        }
    }
}

...

public class SaturatedFat : FoodCountWithDailyValue<decimal>
{
    public SaturatedFat()
    {
        mUnit = Unit.g;
    }

}

public class Fiber : FoodCount<int>
{
    public Fiber()
    {
        mUnit = Unit.g;
    }
}

public void DoSomething()
{
       nutritionFields.SaturatedFat oSatFat = new nutritionFields.SaturatedFat();

       string mstrValueToDisplayPreFormatted= oSatFat.ToString();
}

Vì vậy, tóm lại, tôi nghĩ rằng có những lý do hợp lệ tại sao bạn có thể muốn kiểm tra xem loại chung chung là gì, để làm điều gì đó đặc biệt.


2

Không có cách nào để sử dụng câu lệnh switch cho những gì bạn muốn nó làm. Câu lệnh switch phải được cung cấp các kiểu tích phân, không bao gồm các kiểu phức tạp như đối tượng "Loại" hoặc bất kỳ kiểu đối tượng nào khác cho vấn đề đó.


2

Việc xây dựng của bạn hoàn toàn đánh bại mục đích của một phương pháp chung chung. Nó xấu về mục đích vì phải có một cách tốt hơn để đạt được những gì bạn đang cố gắng hoàn thành, mặc dù bạn chưa cung cấp cho chúng tôi đầy đủ thông tin để tìm ra đó là gì.


2

Bạn có thể làm typeOf(T) , nhưng tôi sẽ kiểm tra lại phương pháp của bạn và đảm bảo rằng bạn không vi phạm trách nhiệm đơn lẻ ở đây. Đây sẽ là một mùi mã, và điều đó không có nghĩa là không nên làm nhưng bạn nên thận trọng.

Điểm của generic là có thể xây dựng các biệt hiệu kiểu bất khả tri là bạn không cần quan tâm đến kiểu đó là gì hoặc miễn là nó phù hợp với một bộ tiêu chí nhất định. Cách triển khai của bạn không chung chung lắm.


2

Tôi hy vọng bạn tìm thấy điều này hữu ích:

  • typeof(IList<T>).IsGenericType == true
  • typeof(IList<T>).GetGenericTypeDefinition() == typeof(IList<>)
  • typeof(IList<int>).GetGenericArguments()[0] == typeof(int)

https://dotnetfiddle.net/5qUZnt


0

Còn cái này thì sao :

            // Checks to see if the value passed is valid. 
            if (!TypeDescriptor.GetConverter(typeof(T)).IsValid(value))
            {
                throw new ArgumentException();
            }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.