GetType () โกหกได้ไหม?


94

จากคำถามต่อไปนี้ที่ถามเมื่อไม่กี่วันที่ผ่านมาใน SO: GetType () และความหลากหลายและอ่านคำตอบของ Eric Lippertฉันเริ่มคิดว่าถ้าทำให้GetType()ไม่เสมือนจริงทำให้มั่นใจได้ว่าวัตถุไม่สามารถโกหกTypeได้

โดยเฉพาะคำตอบของ Eric ระบุสิ่งต่อไปนี้:

นักออกแบบเฟรมเวิร์กจะไม่เพิ่มคุณสมบัติที่อันตรายอย่างเหลือเชื่อเช่นการปล่อยให้วัตถุโกหกเกี่ยวกับประเภทของมันเพียงเพื่อให้มันสอดคล้องกับวิธีการอื่น ๆ อีกสามวิธีในประเภทเดียวกัน

ตอนนี้คำถามคือ: ฉันสามารถทำให้วัตถุที่ไม่โกหกเกี่ยวกับประเภทของมันโดยไม่ได้รับที่ชัดเจนได้ทันทีหรือไม่ ฉันอาจจะคิดผิดอย่างสุดซึ้งที่นี่และฉันชอบที่จะชี้แจงหากเป็นเช่นนั้น แต่ให้พิจารณารหัสต่อไปนี้:

public interface IFoo
{
    Type GetType();
}

และการใช้งานอินเทอร์เฟซดังกล่าวสองรายการต่อไปนี้:

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

public class NiceFoo : IFoo
{
}

จากนั้นถ้าคุณเรียกใช้โปรแกรมง่ายๆต่อไปนี้:

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();
}

พอแน่ใจว่าผลผิดพลาดbadFooType

ตอนนี้ฉันไม่รู้ว่าสิ่งนี้มีผลกระทบร้ายแรงใด ๆ จากการที่เอริคอธิบายพฤติกรรมนี้ว่าเป็น " ลักษณะที่อันตรายอย่างไม่น่าเชื่อ " แต่รูปแบบนี้อาจเป็นภัยคุกคามที่น่าเชื่อถือได้หรือไม่


3
ชื่อเรื่องและหัวข้อที่น่าสนใจ!
เดวิด

43
IFoo.GetTypeและobject.GetTypeไม่ใช่สิ่งเดียวกันดังนั้นจึงไม่มีอะไรเลวร้ายเกิดขึ้นที่นี่ยกเว้นรูปแบบที่ไม่ดี แก้ไข: โดยทั่วไปGetTypeจะเรียกวัตถุบางอย่างที่ไม่รู้จักในเวลาคอมไพล์ในกรณีส่วนใหญ่objectไม่ใช่อินเทอร์เฟซที่หลบหลีก :)
leppie

4
ชื่อของคุณครับทำให้วันของฉัน
Soner Gönül

5
คุณเพิ่งแนะนำสมาชิกใหม่ที่เรียกว่าGetTypeด้วยลายเซ็นเดียวกัน นั่นไม่เกี่ยวข้องกับGetTypeวิธีการซึ่งเป็นสิ่งสำคัญ คุณยังสามารถสร้างวิธีการอินสแตนซ์สาธารณะซึ่งซ่อนGetTypeเมธอดที่เกี่ยวข้องได้โดยใช้newคีย์เวิร์ดตัวปรับแต่ง โปรดทราบว่าหากคุณมีวิธีการทั่วไปเช่นstatic Type Test<T>(T t) { return t.GetType(); }(โดยไม่มีข้อ จำกัดT) สิ่งต่างๆเช่นTest<IFoo>(new BadFoo())นี้จะยังคงเรียกGetTypeวิธีการเดิม
Jeppe Stig Nielsen

2
@Jamiec - คำถาม "รูปแบบนี้อาจก่อให้เกิดภัยคุกคามที่น่าเชื่อถือได้หรือไม่" ไม่ใช่วาทศิลป์
Martin Smith

คำตอบ:


45

เป็นคำถามที่ดี! อย่างที่ฉันเห็นคุณสามารถทำให้นักพัฒนาคนอื่นเข้าใจผิดได้ก็ต่อเมื่อ GetType เป็นเสมือนบนวัตถุซึ่งไม่ใช่

สิ่งที่คุณทำคล้ายกับการแชโดว์ GetType เช่นนี้:

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

ด้วยคลาสนี้ (และใช้โค้ดตัวอย่างจาก MSDN สำหรับเมธอด GetType () ) คุณสามารถมี:

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

คุณโกหกสำเร็จแล้วใช่มั้ย? ใช่และไม่ใช่ ... พิจารณาว่าการใช้สิ่งนี้เป็นการหาประโยชน์จะหมายถึงการใช้อินสแตนซ์ BadFoo ของคุณเป็นอาร์กิวเมนต์สำหรับวิธีการใดที่หนึ่งซึ่งคาดว่าน่าจะเป็นobjectหรือประเภทฐานทั่วไปสำหรับลำดับชั้นของวัตถุ สิ่งนี้:

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");
    }
}

แต่CheckIfInt(foo)พิมพ์ "ไม่ใช่ int"

ดังนั้นโดยทั่วไป (กลับไปตัวอย่างเช่นคุณ) คุณสามารถใช้ประโยชน์จริงๆเท่านั้น "ประเภทโกหก" ของคุณด้วยรหัสที่มีคนเขียนกับคุณIFooอินเตอร์เฟซซึ่งเป็นมากอย่างชัดเจนเกี่ยวกับความจริงที่ว่ามันมี "กำหนดเอง" GetType()วิธีการ

เฉพาะในกรณีที่ GetType () เป็นเสมือนบนออบเจ็กต์คุณจะสามารถสร้างประเภท "โกหก" ที่สามารถใช้กับวิธีการต่างๆCheckIfIntข้างต้นเพื่อสร้างความเสียหายในไลบรารีที่เขียนโดยบุคคลอื่น


ใช่มันเหมือนกับการทำเงา ย่อหน้าสุดท้ายคือสิ่งที่ทำให้เห็นได้ชัดว่าไม่มีภัยคุกคามใด ๆ ขอบคุณ!
ระหว่าง

32

มีสองวิธีในการตรวจสอบประเภท:

  1. ใช้typeofกับ Type ที่ไม่สามารถโอเวอร์โหลดได้

    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. ส่งอินสแตนซ์ไปที่objectและเรียกใช้GetType()เมธอด

    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
คุณจะใช้วิธีใดtypeofในวิธีการที่ได้รับIFoo badFooพารามิเตอร์เป็นเท่านั้น
huysentruitw

typeofไม่สามารถใช้กับอินสแตนซ์ของคลาสซึ่งเป็นสิ่งที่เราต้องทำที่นี่ GetType()ตัวเลือกเดียวของคุณคือ
ระหว่าง

สองตัวอย่างของคุณกำลังทำสองสิ่งที่แตกต่างกัน บรรทัดที่สองไม่ตอบคำถามที่ต้องการ - "รับประเภทBadFoo" อย่างชัดเจนแต่ไม่ "รับประเภทของตัวแปรbadFoo"
Dan Puzey

ใช่ฉันขอโทษ ตามปกติฉันไม่ได้อ่านคำถามอย่างละเอียดเพียงพอ ฉันอัปเดตคำตอบเพื่อชี้ให้เห็นถึงสองวิธีที่แตกต่างกันในการตรวจสอบประเภท
Johannes Wanzek

1
นี่คือสิ่งที่ฉันชี้ให้เห็น แล้วความคิดเห็นของคุณคืออะไร? :)
Johannes Wanzek

10

ไม่คุณไม่สามารถโกหก GetType ได้ คุณกำลังแนะนำวิธีการใหม่เท่านั้น เฉพาะรหัสที่ทราบถึงวิธีนี้เท่านั้นที่จะเรียกมัน

ตัวอย่างเช่นคุณไม่สามารถสร้างรหัสของบุคคลที่สามหรือรหัสกรอบงานเรียกเมธอด GetType ใหม่ของคุณแทนวิธีการจริงได้เนื่องจากรหัสนั้นไม่ทราบว่ามีวิธีการของคุณอยู่และจะไม่เรียกมัน

อย่างไรก็ตามคุณสามารถสร้างความสับสนให้กับนักพัฒนาซอฟต์แวร์ของคุณเองด้วยคำประกาศดังกล่าว รหัสใด ๆ ที่รวบรวมด้วยการประกาศของคุณและใช้พารามิเตอร์หรือตัวแปรที่พิมพ์เป็น IFoo หรือประเภทใด ๆ ที่ได้มาจากนั้นจะใช้วิธีการใหม่ของคุณแทน แต่เนื่องจากสิ่งนี้มีผลต่อโค้ดของคุณเท่านั้นจึงไม่ได้กำหนดให้เป็น "ภัยคุกคาม"

หากคุณไม่ต้องการที่จะให้คำอธิบายประเภทที่กำหนดเองสำหรับการเรียนนี้ควรจะทำโดยใช้ประเภทที่กำหนดเองอธิบายอาจจะโดย annotating ชั้นเรียนของคุณกับTypeDescriptionProviderAttribute สิ่งนี้จะมีประโยชน์ในบางสถานการณ์


2
+1 สำหรับย่อหน้าที่สองของคุณชี้ให้เห็นอย่างชัดเจนว่ารหัสของบุคคลที่สามไม่ทราบเกี่ยวกับการใช้งาน GetType ที่กำหนดเอง คำตอบอื่น ๆ บ่งบอกถึงความคิดนั้น แต่ไม่ได้ออกมาพูดจริงๆ (อย่างน้อยก็ไม่ชัดเจนเท่า)
brichins

7

ที่จริงมีอยู่แล้วประเภทหนึ่งที่สามารถอยู่ได้GetType: ประเภทที่ว่างเปล่า

รหัสนี้ :

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

Trueเอาท์พุท


จริงๆแล้วไม่ใช่int?ใครโกหก แต่โดยปริยายโยนให้objectกลายint?เป็นบ็intอกซ์ แต่อย่างไรก็ตามคุณไม่สามารถบอกได้int?จากการที่มีintGetType()


1
ซึ่งเป็นที่คาดหวัง (หรืออย่างน้อยก็เป็นที่รู้จัก) พฤติกรรม คำตอบของคำถามนี้อธิบายแนวคิดนี้อย่างชัดเจนรวมทั้งเหตุผล
brichins

@brichins: ฉันยอมรับว่าเป็นที่รู้จัก แต่ฉันไม่สามารถยอมรับได้ว่าเป็นที่รู้จักกันดี อย่างไรก็ตามนี่เป็นกรณีที่GetType()ให้ผลลัพธ์ที่ค่อนข้างแปลก ฉันได้ถามเพื่อนร่วมงานหลายคนจริง ๆ ว่าสิ่งที่ไม่เป็นเงาGetType()สามารถส่งคืนสิ่งที่แตกต่างจากประเภทรันไทม์ของวัตถุจริงได้หรือไม่คำตอบของทุกคนคือ 'ไม่'
Vlad

5

ฉันไม่คิดว่าจะเป็นเช่นนั้นเนื่องจากรหัสไลบรารีทุกตัวที่เรียก GetType จะประกาศตัวแปรเป็น 'Object' หรือเป็น Generic type 'T'

รหัสต่อไปนี้:

    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());
    }

พิมพ์:

Object BadFoo บอกว่าเขาเป็น 'TypeConcept.BadFoo'

Object NiceFoo บอกว่าเขาเป็น 'TypeConcept.NiceFoo'

ประเภททั่วไป BadFoo บอกว่าเขาเป็น 'TypeConcept.BadFoo'

ประเภททั่วไป NiceFoo บอกว่าเขาเป็น 'TypeConcept.NiceFoo'

ครั้งเดียวที่รหัสประเภทนี้จะส่งผลให้สถานการณ์เลวร้ายอยู่ในรหัสของคุณเองโดยที่คุณประกาศประเภทพารามิเตอร์เป็น 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 บอกว่าเขาเป็น 'System.Int32'

IFoo NiceFoo บอกว่าเขาเป็น 'TypeConcept.NiceFoo'


4

สิ่งที่เลวร้ายที่สุดที่สามารถเกิดขึ้นได้เท่าที่ฉันสามารถบอกได้คือโปรแกรมเมอร์ผู้บริสุทธิ์ที่ทำให้เข้าใจผิดซึ่งบังเอิญใช้คลาสที่เป็นพิษเช่น:

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";
}

หากmyInstanceเป็นตัวอย่างของชั้นเรียนเช่นที่คุณอธิบายไว้ในคำถามนั้นจะถือว่าเป็นประเภทที่ไม่รู้จัก

ดังนั้นคำตอบของฉันคือไม่ไม่เห็นภัยคุกคามที่แท้จริงที่นี่


1
แน่นอน โปรแกรมเมอร์ที่ระมัดระวังสามารถมองเห็นได้ในเวลาคอมไพล์ว่าเขาเรียกใช้เมธอด "GetType" ใด แตกต่างจากObject.GetType() SomeUserdefinedInterfaceClassOrStruct.GetType()เฉพาะในกรณีที่คุณใช้dynamicประเภทคุณจะไม่มีทางรู้ว่าจะเกิดอะไรขึ้นในเวลาผูกมัด ดังนั้นคุณควรใช้dynamic x = expression; ... Type t = ((object)x).GetType();ในกรณีเช่นนั้น
Jeppe Stig Nielsen

@Jeppe ชี้ธรรม! ฉันคิดว่ามันแสดงให้เห็นถึงคำตอบที่แยกจากกันคำตอบของฉันมุ่งเน้นไปที่โปรแกรมเมอร์ "ไร้เดียงสา" มากกว่าที่จะไม่ระมัดระวัง
Shadow Wizard is Ear For You

3

คุณมีทางเลือกบางอย่างหากต้องการเล่นอย่างปลอดภัยจากการแฮ็กประเภทนั้น:

ส่งเพื่อคัดค้านก่อน

คุณสามารถเรียกใช้GetType()เมธอดเดิมได้โดยการแคสต์อินสแตนซ์ไปที่object:

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

ผลลัพธ์ใน:

BadFoo says he's a 'ConsoleApplication.BadFoo'

ใช้วิธีเทมเพลต

การใช้วิธีเทมเพลตนี้จะทำให้คุณมีประเภทจริง:

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

GetType(badFoo);

2

มีความแตกต่างระหว่างการเป็นและobject.GetType ถูกเรียกในเวลาคอมไพล์บนวัตถุที่ไม่รู้จักไม่ใช่ในอินเทอร์เฟซ ในตัวอย่างของคุณมีเอาต์พุตเป็นที่คาดหวังเนื่องจากคุณใช้เมธอดมากเกินไป สิ่งเดียวคือโปรแกรมเมอร์คนอื่นอาจสับสนกับพฤติกรรมนี้ได้IFoo.GetTypeGetTypebadFoo.GetType

แต่ถ้าคุณใช้typeof()มันจะแสดงผลว่าเป็นประเภทเดียวกันและคุณไม่สามารถเขียนทับtypeof()ได้

นอกจากนี้โปรแกรมเมอร์ยังสามารถดูได้ในเวลารวบรวมว่าGetTypeเขาเรียกใช้วิธีใด

สำหรับคำถามของคุณ: รูปแบบนี้ไม่สามารถก่อให้เกิดภัยคุกคามที่น่าเชื่อถือได้ แต่ก็ไม่ใช่รูปแบบการเข้ารหัสที่ดีที่สุด


badFoo.GetType()เป็นพฤติกรรมที่คาดหวังเนื่องจากGetTypeมีงานมากเกินไป
huysentruitw
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.