GetType () có thể nói dối?


94

Dựa trên câu hỏi sau được hỏi vài ngày trước trong SO: GetType () và tính đa hình và đọc câu trả lời của Eric Lippert , tôi bắt đầu nghĩ rằng nếu việc tạo GetType()không ảo thực sự đảm bảo rằng một đối tượng không thể nói dối về nó Type.

Cụ thể, câu trả lời của Eric nêu rõ như sau:

Các nhà thiết kế khung sẽ không thêm một tính năng cực kỳ nguy hiểm như cho phép một đối tượng nói dối về kiểu của nó chỉ để làm cho nó phù hợp với ba phương pháp khác trên cùng một kiểu.

Bây giờ câu hỏi là: Tôi có thể làm cho một đối tượng mà không nói dối về loại hình của nó mà không có nó là ngay lập tức rõ ràng? Tôi có thể đã sai sâu sắc ở đây và tôi muốn làm rõ nếu đúng như vậy, nhưng hãy xem xét đoạn mã sau:

public interface IFoo
{
    Type GetType();
}

Và hai cách triển khai sau của giao diện nói trên:

public class BadFoo : IFoo
{
    Type IFoo.GetType()
    {
        return typeof(int);
    }
}

public class NiceFoo : IFoo
{
}

Sau đó, nếu bạn chạy chương trình đơn giản sau:

static void Main(string[] args)
{
    IFoo badFoo = new BadFoo();
    IFoo niceFoo = new NiceFoo();
    Console.WriteLine("BadFoo says he's a '{0}'", badFoo.GetType().ToString());
    Console.WriteLine("NiceFoo says he's a '{0}'", niceFoo.GetType().ToString());
    Console.ReadLine();
}

Chắc chắn đủ badFoođầu ra một lỗi Type.

Bây giờ tôi không biết liệu điều này có bất kỳ tác động nghiêm trọng nào không dựa trên việc Eric mô tả hành vi này là một " tính năng cực kỳ nguy hiểm ", nhưng mô hình này có thể gây ra mối đe dọa đáng tin cậy không?


3
tiêu đề và chủ đề thú vị!
— David

43
IFoo.GetTypevà object.GetTypekhông giống nhau nên không có gì xấu đang xảy ra ở đây ngoại trừ phong cách kém. Chỉnh sửa: Nói chung GetTypesẽ được gọi trên một số đối tượng không được biết đến tại thời điểm biên dịch, trong hầu hết các trường hợp objectvà không phải một số giao diện tinh ranh. :)
— leppie

4
Danh hiệu của bạn thưa ngài, đã làm nên ngày của tôi.
— Soner Gönül

5
Bạn vừa giới thiệu thành viên mới cũng được gọi tên GetType, cùng chữ ký. Điều đó không liên quan đến GetTypephương pháp mà là quan trọng. Bạn cũng có thể tạo một phương thức phiên bản công khai ẩn GetTypephương thức có liên quan , bằng cách sử dụng newtừ khóa bổ trợ. Lưu ý, nếu bạn có một phương thức chung như static Type Test<T>(T t) { return t.GetType(); }(không có ràng buộc T), thì nội dung như Test<IFoo>(new BadFoo())sẽ vẫn gọi GetTypephương thức gốc .
— Jeppe Stig Nielsen

2
@Jamiec - Câu hỏi "mô hình này có thể gây ra mối đe dọa đáng tin cậy không?" không khoa trương.
— Martin Smith

Câu trả lời:


45

Câu hỏi hay! Theo cách tôi thấy, bạn chỉ có thể thực sự đánh lừa một nhà phát triển đồng nghiệp nếu GetType là ảo trên đối tượng, không phải vậy.

Những gì bạn đã làm tương tự như phủ bóng GetType, như thế này:

public class BadFoo
{
    public new Type GetType()
    {
        return typeof(int);
    }
}

với lớp này (và sử dụng mã mẫu từ MSDN cho phương thức GetType () ), bạn thực sự có thể có:

int n1 = 12;
BadFoo foo = new BadFoo();

Console.WriteLine("n1 and n2 are the same type: {0}",
                  Object.ReferenceEquals(n1.GetType(), foo.GetType())); 
// output: 
// n1 and n2 are the same type: True

Vì vậy, bạn đã nói dối thành công, phải không? Vâng, có và không ... Hãy xem xét rằng việc sử dụng điều này như một khai thác có nghĩa là sử dụng cá thể BadFoo của bạn như một đối số cho một phương thức ở đâu đó, điều đó mong đợi có thể là một objecthoặc một kiểu cơ sở chung cho một hệ thống phân cấp của các đối tượng. Một cái gì đó như thế này:

public void CheckIfInt(object ob)
{
    if(ob.GetType() == typeof(int))
    {
        Console.WriteLine("got an int! Initiate destruction of Universe!");
    }
    else
    {
        Console.WriteLine("not an int");
    }
}

nhưng CheckIfInt(foo)in ra "không phải là số nguyên".

Vì vậy, về cơ bản (trở lại ví dụ của bạn), bạn thực sự chỉ có thể khai thác "kiểu nói dối" của mình với mã mà ai đó đã viết dựa trên IFoogiao diện của bạn , điều này rất rõ ràng về thực tế là nó có GetType()phương thức "tùy chỉnh" .

Chỉ khi GetType () là ảo trên đối tượng, bạn mới có thể tạo kiểu "nằm" có thể được sử dụng với các phương thức như CheckIfInttrên để tạo tàn phá trong thư viện do người khác viết.


yeah, nó giống hệt như đổ bóng. Đoạn cuối là những gì thực sự làm cho nó rõ ràng rằng thực sự không có mối đe dọa. Cảm ơn!
— InBetween

32

Có hai cách để chắc chắn về Loại:

  1. Sử dụng typeoftrên Loại không thể quá tải

    IFoo badFoo = new BadFoo();
    IFoo niceFoo = new NiceFoo();
    
    Console.WriteLine("BadFoo says he's a '{0}'", badFoo.GetType().ToString());
    Console.WriteLine("NiceFoo says he's a '{0}'", niceFoo.GetType().ToString());
    
    Console.WriteLine("BadFoo really is a '{0}'", typeof(BadFoo));
    Console.WriteLine("NiceFoo really is a '{0}'", typeof(NiceFoo));
    Console.ReadLine();
    
  2. Truyền phiên bản tới một objectvà gọi GetType()Phương thức

    IFoo badFoo = new BadFoo();
    IFoo niceFoo = new NiceFoo();
    
    Console.WriteLine("BadFoo says he's a '{0}'", badFoo.GetType().ToString());
    Console.WriteLine("NiceFoo says he's a '{0}'", niceFoo.GetType().ToString());
    
    Console.WriteLine("BadFoo really is a '{0}'", ((object)badFoo).GetType());
    Console.WriteLine("NiceFoo really is a '{0}'", ((object)niceFoo).GetType());
    Console.ReadLine();
    

1
Bạn sẽ sử dụng như thế nào typeoftrong một phương thức chỉ nhận một IFoo badFootham số là?
— huysentruitw

typeofkhông thể được áp dụng cho các trường hợp của một lớp, đó là những gì chúng ta cần làm ở đây. Lựa chọn duy nhất của bạn là GetType().
— InBetween

Hai mẫu của bạn đang làm hai việc khác nhau. Các dòng thứ hai không trả lời câu hỏi bắt buộc - chúng rõ ràng là "nhận loại BadFoo" nhưng không "lấy loại biến badFoo".
— Dan Puzey

Vâng, tôi xin lỗi. Như thường lệ, tôi đã không đọc kỹ câu hỏi. Tôi đã cập nhật câu trả lời của mình để chỉ ra hai cách khác nhau để chắc chắn về loại.
— Johannes Wanzek

1
Đây là những gì tôi đã chỉ ra. Vậy ý kiến ​​của bạn là gì? :)
— Johannes Wanzek

10

Không, bạn không thể khiến GetType nói dối. Bạn chỉ đang giới thiệu một phương pháp mới. Chỉ mã nhận biết được phương thức này mới gọi nó.

Ví dụ, bạn không thể làm cho bên thứ ba hoặc mã khung gọi phương thức GetType mới của bạn thay vì phương thức thực, vì mã đó không biết rằng phương thức của bạn tồn tại và do đó sẽ không bao giờ gọi nó.

Tuy nhiên, bạn có thể gây nhầm lẫn cho các nhà phát triển của chính mình với một khai báo như vậy. Bất kỳ mã nào được biên dịch với khai báo của bạn và sử dụng các tham số hoặc biến được nhập dưới dạng IFoo hoặc bất kỳ kiểu nào bắt nguồn từ đó thực sự sẽ sử dụng phương pháp mới của bạn. Nhưng vì điều đó chỉ ảnh hưởng đến mã của riêng bạn nên nó không thực sự áp đặt một "mối đe dọa".

Nếu bạn muốn cung cấp mô tả kiểu tùy chỉnh cho một lớp, điều này nên được thực hiện bằng Bộ mô tả kiểu tùy chỉnh , có thể bằng cách chú thích lớp của bạn bằng TypeDescriptionProviderAttribute . Điều này có thể hữu ích trong một số tình huống.


2
+1 cho đoạn thứ hai của bạn chỉ ra rõ ràng rằng mã của bên thứ 3 không biết về triển khai GetType tùy chỉnh. Các câu trả lời khác gợi ý về ý tưởng đó nhưng không thực sự đưa ra và nói nó (ít nhất là không rõ ràng).
— brichins

7

Vâng, thực sự có là đã là một loại mà có thể nằm ở GetType: bất kỳ loại nullable.

Mã này :

int? x = 0; int y = 0;
Console.WriteLine(x.GetType() == y.GetType());

kết quả đầu ra True.


Thực ra không phải int?ai đang nói dối, chỉ là ẩn ý để objectbiến int?thành đồ hộp intthôi. Nhưng tuy nhiên bạn không thể biết int?từ intvới GetType().


1
Hành vi được mong đợi (hoặc ít nhất là nổi tiếng). Câu trả lời cho câu hỏi này giải thích khái niệm này khá rõ ràng, cũng như lý do của nó.
— brichins

@zhenhins: Chà, tôi đồng ý là nó đã biết, nhưng tôi không thể đồng ý rằng nó-không rõ . Dù sao, đây là một trường hợp GetType()tạo ra kết quả hơi kỳ lạ. Tôi đã thực sự hỏi một số đồng nghiệp về việc liệu một không bị che khuất GetType()có thể trả về thứ gì đó khác với kiểu thời gian chạy của đối tượng thực tế hay không, câu trả lời của mọi người là 'không'.
— Vlad

5

Tôi không nghĩ là nó sẽ xảy ra, vì mọi mã thư viện gọi GetType sẽ khai báo biến là 'Đối tượng' hoặc là kiểu Chung 'T'

Đoạn mã sau:

    public static void Main(string[] args)
    {
        IFoo badFoo = new BadFoo();
        IFoo niceFoo = new NiceFoo();
        PrintObjectType("BadFoo", badFoo);
        PrintObjectType("NiceFoo", niceFoo);
        PrintGenericType("BadFoo", badFoo);
        PrintGenericType("NiceFoo", niceFoo);
    }

    public static void PrintObjectType(string actualName, object instance)
    {
        Console.WriteLine("Object {0} says he's a '{1}'", actualName, instance.GetType());
    }

    public static void PrintGenericType<T>(string actualName, T instance)
    {
        Console.WriteLine("Generic Type {0} says he's a '{1}'", actualName, instance.GetType());
    }

bản in:

Object BadFoo nói rằng anh ấy là một 'TypeConcept.BadFoo'

Object NiceFoo cho biết anh ấy là một 'TypeConcept.NiceFoo'

Loại chung chung BadFoo nói rằng anh ấy là một 'TypeConcept.BadFoo'

Loại chung NiceFoo nói rằng anh ấy là một 'TypeConcept.NiceFoo'

Lần duy nhất loại mã này dẫn đến tình huống xấu là trong mã của chính bạn, nơi bạn khai báo loại tham số là IFoo

    public static void Main(string[] args)
    {
        IFoo badFoo = new BadFoo();
        IFoo niceFoo = new NiceFoo();
        PrintIFoo("BadFoo", badFoo);
        PrintIFoo("NiceFoo", niceFoo);
    }

    public static void PrintIFoo(string actualName, IFoo instance)
    {
        Console.WriteLine("IFoo {0} says he's a '{1}'", actualName, instance.GetType());
    }

IFoo BadFoo cho biết anh ấy là 'System.Int32'

IFoo NiceFoo cho biết anh ấy là một 'TypeConcept.NiceFoo'


4

Điều tồi tệ nhất có thể xảy ra theo như tôi có thể nói là gây hiểu lầm cho các lập trình viên vô tội, những người tình cờ sử dụng lớp bị nhiễm độc, ví dụ:

Type type = myInstance.GetType();
string fullName = type.FullName;
string output;
if (fullName.Contains(".Web"))
{
    output = "this is webby";
}
else if (fullName.Contains(".Customer"))
{
    output = "this is customer related class";
}
else
{
    output = "unknown class";
}

Nếu myInstancelà thể hiện của một lớp như bạn mô tả trong câu hỏi, nó sẽ chỉ được coi là kiểu không xác định.

Vì vậy, câu trả lời của tôi là không, không thể nhìn thấy bất kỳ mối đe dọa thực sự nào ở đây.


1
Chắc chắn rồi. Một lập trình viên cẩn thận có thể thấy phương thức "GetType" mà anh ta gọi vào lúc biên dịch. Object.GetType()khác biệt với SomeUserdefinedInterfaceClassOrStruct.GetType(). Chỉ, nếu bạn sử dụng dynamickiểu này, bạn không bao giờ có thể biết điều gì sẽ xảy ra tại thời điểm ràng buộc. Vì vậy, bạn nên sử dụng dynamic x = expression; ... Type t = ((object)x).GetType();trong những trường hợp như vậy.
— Jeppe Stig Nielsen

@Jeppe điểm công bằng! Tôi nghĩ rằng nó biện minh cho câu trả lời riêng biệt, câu trả lời của tôi tập trung nhiều hơn vào lập trình viên "ngây thơ", những người sẽ không cẩn thận như vậy.
— Shadow Wizard là Ear For You

3

Bạn có một số tùy chọn nếu bạn muốn chơi an toàn trước loại hack đó:

Truyền đến đối tượng trước

Bạn có thể gọi GetType()phương thức gốc bằng cách truyền phiên bản đầu tiên tới một object:

 Console.WriteLine("BadFoo says he's a '{0}'", ((object)badFoo).GetType());

kết quả trong:

BadFoo says he's a 'ConsoleApplication.BadFoo'

Sử dụng phương pháp mẫu

Sử dụng phương pháp mẫu này cũng sẽ cung cấp cho bạn kiểu thực:

static Type GetType<T>(T obj)
{
    return obj.GetType();
}

GetType(badFoo);

2

Có một sự khác biệt giữa object.GetTypevà IFoo.GetType. GetTypeđược gọi tại thời điểm biên dịch trên các đối tượng chưa biết, không phải trên Giao diện. Trong ví dụ của bạn, với đầu ra, badFoo.GetTypenó được mong đợi là bahaviour, bởi vì bạn quá tải phương thức. Chỉ có điều, các lập trình viên khác có thể nhầm lẫn với hành vi này.

Nhưng nếu bạn sử dụng typeof()nó sẽ xuất ra rằng loại này giống nhau và bạn không thể ghi đè typeof().

Ngoài ra, lập trình viên có thể xem tại thời điểm biên dịch, phương thức GetTypemà anh ta gọi.

Vì vậy, cho câu hỏi của bạn: Mẫu này không thể gây ra một mối đe dọa đáng tin cậy, nhưng nó cũng không phải là phong cách mã hóa tốt nhất.


badFoo.GetType()Hành vi mong đợi của IS, vì GetTypeđã quá tải.
— huysentruitw
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 cookie và Chính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.