.NET: กำหนดประเภทของคลาส“ this” ในวิธีการคงที่


98

ในวิธีที่ไม่คงที่ฉันสามารถใช้this.GetType()และมันจะส่งคืนไฟล์Type. ฉันจะทำให้เหมือนกันTypeในวิธีการคงที่ได้อย่างไร แน่นอนฉันไม่สามารถเขียนได้typeof(ThisTypeName)เพราะThisTypeNameเป็นที่รู้จักในรันไทม์เท่านั้น ขอบคุณ!


16
คุณอยู่ในบริบท STATIC และไม่สามารถเขียน typeof (ThisTypeName) ได้? อย่างไร?
Bruno Reis

1
ไม่มีอะไรที่เหมือนกับ 'รันไทม์' ในเมธอดแบบคงที่ (สมมติว่าคุณไม่ได้พูดถึงอาร์กิวเมนต์ที่ส่งผ่านไปยังเมธอดแบบคงที่) ในกรณีนี้คุณสามารถพูดว่า typeof (RelevantType)
Manish Basantani

2
วิธีการคงที่ไม่สามารถเป็นเสมือนได้ คุณรู้จักประเภทแล้ว
Hans Passant

7
จะมีคลาสที่ได้รับมากมายจากนามธรรม คลาสนามธรรมพื้นฐานมีพจนานุกรมแบบคงที่ <Int, Type> คลาสที่ได้รับมาจึง "ลงทะเบียน" ในตัวสร้างแบบคงที่ (dic.Add (N, T)) ใช่ฉันรู้จักประเภท :) ฉันขี้เกียจนิดหน่อยและไม่ชอบแทนที่ข้อความและสงสัยว่าสามารถกำหนด "T" ในรันไทม์ได้หรือไม่ ขอแก้ตัวเรื่องโกหกของฉันเพราะมันจำเป็นต้องทำให้คำถามง่ายขึ้น และมันใช้งานได้;) ตอนนี้มีวิธีแก้ปัญหาที่ได้รับการยอมรับแล้ว ขอบคุณ.
Yegor

คลาสย่อยจะสืบทอดเมธอดแบบคงที่ของซูเปอร์คลาสไม่ใช่หรือ มันจะไม่สมเหตุสมผลสำหรับเมธอด superclass static จะมีประโยชน์สำหรับคลาสย่อยทั้งหมดหรือไม่? คงหมายความว่าไม่มีอินสแตนซ์แน่นอนหลักการของรหัสทั่วไปในคลาสฐานทั่วไปใช้กับวิธีการแบบคงที่และวิธีการอินสแตนซ์ได้หรือไม่?
Matt Connolly

คำตอบ:


137

หากคุณกำลังมองหา 1 ซับที่เทียบเท่ากับthis.GetType()วิธีการคงที่ให้ลองทำดังต่อไปนี้

Type t = MethodBase.GetCurrentMethod().DeclaringType

แม้ว่าจะมีราคาแพงกว่าการใช้เพียงtypeof(TheTypeName)อย่างเดียว


1
อันนี้ใช้ได้ดี ขอบคุณ :) ไม่แพงขนาดนั้นเพราะจะเรียกว่าหายาก
Yegor

2
Entrase โดย Jared "แพง" หมายความว่ามีราคาแพงสำหรับโปรเซสเซอร์ซึ่งมักจะหมายถึงช้า แต่เขาบอกว่า "แพงกว่ามาก" หมายความว่าช้ากว่า อาจจะไม่ช้าเลยเว้นแต่คุณจะออกแบบระบบนำทางจรวด
Dan Rosenstark

1
ฉันเคยเห็น GetCurrentMethod ทำให้เกิดปัญหาด้านประสิทธิภาพที่รุนแรง แต่เนื่องจากคุณเพิ่งได้รับประเภทที่คุณสามารถแคชได้
Jonathan Allen

52
สิ่งนี้จะส่งคืนคลาสที่ใช้เมธอดปัจจุบันเสมอไม่ใช่คลาสที่ถูกเรียกใช้ในกรณีของคลาสย่อย
Matt Connolly

3
ผมคิดว่ามันเป็นเรื่องที่มีประโยชน์เพื่อหลีกเลี่ยงข้อผิดพลาดถ้าเคยได้รับรหัสอพยพไปชื่อชั้นที่แตกต่างกันหรืออะไร แต่เป็นเครื่องมือที่ดี refactoring ควรดูแลtypeof(TheTypeName)อยู่แล้ว
Nyerguds

59

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

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

UnicodeEncoding.GetEncoding(0);

ตอนนี้ใช้ ildasm กับมัน ... คุณจะเห็นว่ามีการโทรออกมาดังนี้:

IL_0002:  call       class [mscorlib]System.Text.Encoding 
[mscorlib]System.Text.Encoding::GetEncoding(int32)

คอมไพเลอร์ได้แก้ไขการเรียกไปที่Encoding.GetEncoding- ไม่มีร่องรอยUnicodeEncodingด้านซ้าย นั่นทำให้ความคิดของคุณเกี่ยวกับ "ประเภทปัจจุบัน" เป็นเรื่องไร้สาระฉันกลัว


กรอไปข้างหน้า 10 ปีแล้วทำไมยังไม่มีสถิตเสมือนใน C #? ;) โดยปกติแล้วไม่มีใครต้องการพวกเขา ... แต่มีบางครั้งที่หาได้ยากเมื่อพวกเขามีประโยชน์;)
marchewek

@marchewek: มากหายากมากฉันจะบอกว่า - หายากพอที่ทีม C # มักจะพบสิ่งที่มีประโยชน์มากกว่าที่จะทำ (ไม่ใช่ว่าพวกเขาไม่ได้ใช้งานตลอดเวลานี้ ... )
Jon Skeet

24

อีกวิธีหนึ่งคือการใช้ประเภท selfreferecing

//My base class
//I add a type to my base class use that in the static method to check the type of the caller.
public class Parent<TSelfReferenceType>
{
    public static Type GetType()
    {
        return typeof(TSelfReferenceType);
    }
}

จากนั้นในคลาสที่สืบทอดมาฉันสร้างประเภทการอ้างอิงตัวเอง:

public class Child: Parent<Child>
{
}

ตอนนี้ประเภทการโทร typeof (TSelfReferenceType) ภายใน Parent จะได้รับและส่งคืน Type ของผู้โทรโดยไม่จำเป็นต้องมีอินสแตนซ์

Child.GetType();

-ปล้น


ฉันใช้สิ่งนี้สำหรับรูปแบบซิงเกิลตันเช่น Singleton <T> ... จากนั้นสมาชิกแบบคงที่สามารถอ้างถึง typeof (T) ในข้อความแสดงข้อผิดพลาดหรือที่ใดก็ได้ที่จำเป็น
โยโย่

1
สวัสดี. ฉันชอบและขอบคุณคำตอบนี้มากเพราะทำให้ฉันมีวิธีแก้ไขเพื่อค้นหาประเภทลูกจากฟังก์ชันฐานคงที่
Bill Software Engineer

1
ทำได้ดีนี่. น่าเศร้าเกินไปที่ใน C # ไม่สามารถทำได้หากไม่มีการทำซ้ำรหัสเล็กน้อยนี้
ชื่อที่แสดง

มีอีกตัวอย่างหนึ่งของวิธีแก้ปัญหานี้ในstackoverflow.com/a/22532416/448568
Steven de Salas

ทั้งสองอย่าง (อันนี้และอันที่เชื่อมโยงโดยสตีเวน) จะใช้ไม่ได้กับผู้สืบทอดของผู้ใช้คลาสพื้นฐาน ... หลาน ๆ จะลงเอยด้วยประเภทเด็ก ... เสียดาย C # ไม่มีสถิตเสมือน;)
marchewek

5

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

public class Car {
  public static void Drive(Car c) {
    Console.WriteLine("Driving a {0}", c.GetType());
  }
}

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

public class Car {
  public void Drive() { // Remove parameter; doesn't need to be static.
    Console.WriteLine("Driving a {0}", this.GetType());
  }
}

3

ฉันไม่เข้าใจว่าทำไมคุณถึงใช้ typeof (ThisTypeName) ไม่ได้ หากเป็นประเภทที่ไม่ใช่ทั่วไปสิ่งนี้ควรใช้งานได้:

class Foo {
   static void Method1 () {
      Type t = typeof (Foo); // Can just hard code this
   }
}

หากเป็นประเภททั่วไปให้ทำดังนี้

class Foo<T> {
    static void Method1 () {
       Type t = typeof (Foo<T>);
    }
}

ฉันพลาดอะไรบางอย่างที่ชัดเจนที่นี่?


7
สิ่งนี้จะใช้ไม่ได้ถ้าคุณสร้างแถบคลาสที่ได้รับมาจาก Foo จากนั้นคลาสจะสืบทอด Method1 จากนั้นเรียกไปที่ Bar Method1 จะยังคงประมวลผล typeof (Foo) ซึ่งไม่ถูกต้อง Method1 ที่สืบทอดมาควรรู้ว่ามันถูกทำลายใน Bar แล้วรับ typeof (Bar)
JustAMartin

0

เมื่อสมาชิกของคุณอยู่ในสถานะคงที่คุณจะรู้เสมอว่าประเภทใดเป็นส่วนหนึ่งของรันไทม์ ในกรณีนี้:

class A
{
  public static int GetInt(){}

}
class B : A {}

คุณไม่สามารถโทรได้ (แก้ไข: เห็นได้ชัดว่าคุณสามารถดูความคิดเห็นด้านล่าง แต่คุณยังคงโทรหา A):

B.GetInt();

เนื่องจากสมาชิกเป็นแบบคงที่จึงไม่ได้มีส่วนในสถานการณ์การสืบทอด Ergo คุณมักจะรู้ว่าประเภทคือ A


4
คุณสามารถเรียก B.GetInt () - อย่างน้อยคุณก็ทำได้ถ้ามันไม่เป็นส่วนตัว - แต่คอมไพล์จะแปลเป็นคำเรียก A.GetInt () ลองมัน!
Jon Skeet

หมายเหตุเกี่ยวกับความคิดเห็นของจอน: การมองเห็นเริ่มต้นใน C # เป็นแบบส่วนตัวดังนั้นตัวอย่างของคุณจึงไม่ทำงาน
Dan Rosenstark

0

สำหรับจุดประสงค์ของฉันฉันชอบความคิดของ @ T-moty แม้ว่าฉันจะใช้ข้อมูล "ชนิดอ้างอิงตัวเอง" มาหลายปีแล้ว แต่การอ้างอิงคลาสพื้นฐานนั้นทำได้ยากกว่าในภายหลัง

ตัวอย่างเช่น (โดยใช้ @Rob Leclerc ตัวอย่างจากด้านบน):

public class ChildA: Parent<ChildA>
{
}

public class ChildB: Parent<ChildB>
{
}

การทำงานกับรูปแบบนี้อาจเป็นเรื่องท้าทายตัวอย่างเช่น คุณคืนคลาสพื้นฐานจากการเรียกใช้ฟังก์ชันได้อย่างไร

public Parent<???> GetParent() {}

หรือเมื่อพิมพ์หล่อ?

var c = (Parent<???>) GetSomeParent();

ดังนั้นฉันจึงพยายามหลีกเลี่ยงเมื่อทำได้และใช้เมื่อต้อง หากคุณต้องการฉันขอแนะนำให้คุณทำตามรูปแบบนี้:

class BaseClass
{
    // All non-derived class methods goes here...

    // For example:
    public int Id { get; private set; }
    public string Name { get; private set; }
    public void Run() {}
}

class BaseClass<TSelfReferenceType> : BaseClass
{
    // All derived class methods goes here...

    // For example:
    public TSelfReferenceType Foo() {}
    public void Bar(TSelfRefenceType obj) {}
}

ตอนนี้คุณสามารถทำงานกับไฟล์BaseClass. อย่างไรก็ตามมีหลายครั้งเช่นเดียวกับสถานการณ์ปัจจุบันของฉันที่ไม่จำเป็นต้องเปิดเผยคลาสที่ได้รับจากภายในคลาสพื้นฐานและการใช้คำแนะนำของ @M-moty ก็อาจเป็นแนวทางที่ถูกต้อง

อย่างไรก็ตามการใช้โค้ดของ @ M-moty จะใช้ได้ตราบเท่าที่คลาสพื้นฐานไม่มีตัวสร้างอินสแตนซ์ใด ๆ ใน call stack น่าเสียดายที่คลาสพื้นฐานของฉันใช้ตัวสร้างอินสแตนซ์

ดังนั้นนี่คือวิธีการขยายของฉันที่คำนึงถึงตัวสร้าง 'อินสแตนซ์' คลาสพื้นฐาน:

public static class TypeExtensions
{
    public static Type GetDrivedType(this Type type, int maxSearchDepth = 10)
    {
        if (maxSearchDepth < 0)
            throw new ArgumentOutOfRangeException(nameof(maxSearchDepth), "Must be greater than 0.");

        const int skipFrames = 2;  // Skip the call to self, skip the call to the static Ctor.
        var stack = new StackTrace();
        var maxCount = Math.Min(maxSearchDepth + skipFrames + 1, stack.FrameCount);
        var frame = skipFrames;

        // Skip all the base class 'instance' ctor calls. 
        //
        while (frame < maxCount)
        {
            var method = stack.GetFrame(frame).GetMethod();
            var declaringType = method.DeclaringType;

            if (type.IsAssignableFrom(declaringType))
                return declaringType;

            frame++;
        }

        return null;
    }
}

0

แก้ไข วิธีนี้จะใช้ได้เฉพาะเมื่อคุณปรับใช้ไฟล์ PDB กับไฟล์ปฏิบัติการ / ไลบรารีดังที่markmnlชี้ให้ฉันเห็น

มิฉะนั้นจะเป็นปัญหาใหญ่ที่ต้องตรวจพบ: ทำงานได้ดีในการพัฒนา แต่อาจไม่ได้อยู่ในการผลิต


วิธียูทิลิตี้เพียงแค่เรียกวิธีการเมื่อคุณต้องการจากทุกที่ของรหัสของคุณ:

public static Type GetType()
{
    var stack = new System.Diagnostics.StackTrace();

    if (stack.FrameCount < 2)
        return null;

    return (stack.GetFrame(1).GetMethod() as System.Reflection.MethodInfo).DeclaringType;
}

1
StackTrace พร้อมใช้งานในรุ่น Debug เท่านั้น
markmnl

ไม่ถูกต้อง: StackTrace จะพร้อมใช้งานเมื่อคุณปรับใช้ไฟล์. pdb ในโหมดรีลีสด้วย stackoverflow.com/questions/2345957/…
T-moty

ฉันมีประเด็นของคุณ ไม่สามารถยอมรับได้ว่าเมธอดจะใช้ได้เฉพาะเมื่อมีการปรับใช้ไฟล์ PDB ฉันจะแก้ไขคำตอบ
T-moty
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.