การเรียกใช้วิธีการแบบคงที่บนพารามิเตอร์ประเภททั่วไป


107

ฉันหวังว่าจะทำอะไรแบบนี้ แต่ดูเหมือนว่าจะผิดกฎหมายใน C #:


public Collection MethodThatFetchesSomething<T>()
    where T : SomeBaseClass
{
    return T.StaticMethodOnSomeBaseClassThatReturnsCollection();
}

ฉันได้รับข้อผิดพลาดเวลาคอมไพล์: "'T' คือ 'พารามิเตอร์ประเภท' ซึ่งไม่ถูกต้องในบริบทที่กำหนด"

ด้วยพารามิเตอร์ประเภททั่วไปฉันจะเรียกเมธอดแบบคงที่ในคลาสทั่วไปได้อย่างไร ต้องมีวิธีการคงที่ตามข้อ จำกัด


4
ดูblogs.msdn.com/b/ericlippert/archive/2007/06/14/…และblogs.msdn.com/b/ericlippert/archive/2007/06/18/…และblogs.msdn.com/b/ ericlippert / archive / 2007/06/21/3445650.aspxสำหรับข้อมูลเพิ่มเติมเกี่ยวกับหัวข้อนี้
Eric Lippert

คำตอบ:


59

ในกรณีนี้คุณควรเรียกใช้วิธีการแบบคงที่ในประเภทข้อ จำกัด โดยตรง C # (และ CLR) ไม่สนับสนุนวิธีการคงที่เสมือน ดังนั้น:

T.StaticMethodOnSomeBaseClassThatReturnsCollection

... ไม่ต่างอะไรกับ:

SomeBaseClass.StaticMethodOnSomeBaseClassThatReturnsCollection

การดำเนินการผ่านพารามิเตอร์ประเภททั่วไปเป็นทิศทางที่ไม่จำเป็นและด้วยเหตุนี้จึงไม่รองรับ


25
แต่ถ้าคุณปิดบังวิธีการคงที่ในคลาสย่อยล่ะ? คลาสสาธารณะ SomeChildClass: SomeBaseClass {ใหม่สาธารณะ StaticMethodOnSomeBaseClassThatReturnsCollection () {}} คุณสามารถเข้าถึงวิธีการคงที่จากประเภททั่วไปได้หรือไม่
Hugo Migneron

2
ลองดูคำตอบของ Joshua Pech ด้านล่างฉันเชื่อว่าจะได้ผลในกรณีนั้น
Remi Despres-Smyth

1
จะreturn SomeBaseClass.StaticMethodOnSomeBaseClassThatReturnsCollection();ทำงาน? ในกรณีนี้คุณอาจต้องการเพิ่มคำตอบของคุณ ขอบคุณ. มันได้ผลสำหรับฉัน ในกรณีของฉันคลาสของฉันมีพารามิเตอร์ประเภทดังนั้นฉันจึงได้return SomeBaseClass<T>.StaticMethodOnSomeBaseClassThatReturnsCollection();ผล
Toddmo

27

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

public void doSomething<T>() where T : someParent
{
    List<T> items=(List<T>)typeof(T).GetMethod("fetchAll").Invoke(null,new object[]{});
    //do something with items
}

โดยที่ T คือคลาสใด ๆ ที่มีวิธีการแบบคงที่ fetchAll ()

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


2
ไม่เลย. JaredPar เข้าใจถูกแล้ว: T.StaticMethodOnSomeBaseClassThatReturnsCollection โดยที่ T: SomeBaseClass ไม่แตกต่างจากการระบุ SomeBaseClass.StaticMethodOnSomeBaseClassThatReturnsCollection
Remi Despres-Smyth

2
นี่คือสิ่งที่ฉันต้องการมันใช้ได้กับวิธีคงที่
myro

นี่คือคำตอบที่ฉันต้องการเพราะฉันไม่ได้ควบคุมคลาสและคลาสพื้นฐาน
ทิม

8

วิธีเดียวในการเรียกใช้วิธีดังกล่าวคือผ่านการสะท้อนกลับอย่างไรก็ตามดูเหมือนว่าอาจเป็นไปได้ที่จะรวมฟังก์ชันการทำงานนั้นไว้ในอินเทอร์เฟซและใช้รูปแบบ IoC / factory / etc ที่อิงอินสแตนซ์


5

ดูเหมือนคุณกำลังพยายามใช้ generics เพื่อหลีกเลี่ยงข้อเท็จจริงที่ว่าไม่มี "virtual static method" ใน C #

น่าเสียดายที่มันไม่ได้ผล


1
ฉันไม่ใช่ - ฉันกำลังทำงานเหนือเลเยอร์ DAL ที่สร้างขึ้น คลาสที่สร้างขึ้นทั้งหมดสืบทอดมาจากคลาสพื้นฐานซึ่งมีเมธอด FetchAll แบบคงที่ ฉันกำลังพยายามลดการทำซ้ำโค้ดในคลาสพื้นที่เก็บข้อมูลของฉันด้วยคลาสพื้นที่เก็บข้อมูล "ทั่วไป" ซึ่งมีโค้ดซ้ำจำนวนมากยกเว้นคลาสคอนกรีตที่ใช้
Remi Despres-Smyth

1
แล้วทำไมคุณไม่เรียก SomeBaseClass.StaticMethod ... ()?
Brad Wilson

ขออภัยฉันอธิบายตัวเองได้ไม่ดีในความคิดเห็นก่อนหน้านี้ FetchAll ถูกกำหนดบนฐาน แต่นำไปใช้กับคลาสที่ได้รับ ฉันต้องการเรียกมันในคลาสที่ได้รับ
Remi Despres-Smyth

7
หากเป็นวิธีการคงที่ก็จะถูกกำหนดและดำเนินการโดยฐาน ไม่มีสิ่งที่เรียกว่าวิธีการคงที่เสมือน / นามธรรมใน C # และไม่มีการแทนที่สำหรับสิ่งนั้น ฉันสงสัยว่าคุณเพิ่งประกาศซ้ำซึ่งแตกต่างกันมาก
Marc Gravell

1
ใช่คุณพูดถูก - ฉันตั้งสมมติฐานที่ไม่ถูกต้องที่นี่ ขอบคุณสำหรับการสนทนานี้ช่วยให้ฉันไปถูกทาง
Remi Despres-Smyth

2

ณ ตอนนี้คุณไม่สามารถ คุณต้องมีวิธีบอกคอมไพเลอร์ว่า T มีวิธีการนั้นและในปัจจุบันไม่มีวิธีทำเช่นนั้น (หลายคนกำลังผลักดันให้ Microsoft ขยายสิ่งที่สามารถระบุได้ในข้อ จำกัด ทั่วไปดังนั้นอาจเป็นไปได้ในอนาคต)


1
ปัญหาคือเนื่องจากข้อมูลทั่วไปถูกจัดเตรียมโดยรันไทม์นี่อาจหมายถึง CLR เวอร์ชันใหม่ซึ่งพวกเขาหลีกเลี่ยง (ส่วนใหญ่) ตั้งแต่ 2.0 บางทีเราอาจจะมีคนใหม่ แต่ ...
Marc Gravell

2

ที่นี่ฉันโพสต์ตัวอย่างที่ใช้งานได้มันเป็นวิธีแก้ปัญหาชั่วคราว

public interface eInterface {
    void MethodOnSomeBaseClassThatReturnsCollection();
}

public T:SomeBaseClass, eInterface {

   public void MethodOnSomeBaseClassThatReturnsCollection() 
   { StaticMethodOnSomeBaseClassThatReturnsCollection() }

}

public Collection MethodThatFetchesSomething<T>() where T : SomeBaseClass, eInterface
{ 
   return ((eInterface)(new T()).StaticMethodOnSomeBaseClassThatReturnsCollection();
}

2
สิ่งนี้ทำให้เกิดข้อผิดพลาดทางไวยากรณ์สำหรับฉัน? อะไรpublic T : SomeBaseClassหมายถึง?
Eric

หากคลาสของคุณมีวิธีการอินสแตนซ์ someInstanceMethod () คุณสามารถเรียกสิ่งนั้นได้ตลอดเวลาโดยทำ (T ใหม่ ()) someInstanceMethod (); - แต่นี่เรียกวิธีการอินสแตนซ์ - คำถามถามว่าจะเรียกเมธอดแบบคงที่ของคลาสได้อย่างไร
ทิโมธี

2

ฉันแค่อยากจะโยนมันออกไปที่นั่นบางครั้งผู้ได้รับมอบหมายสามารถแก้ปัญหาเหล่านี้ได้โดยขึ้นอยู่กับบริบท

หากคุณต้องการเรียกวิธีการแบบคงที่ว่าเป็นวิธีการเริ่มต้นจากโรงงานหรือวิธีการเริ่มต้นคุณสามารถประกาศผู้รับมอบสิทธิ์และส่งวิธีการแบบคงที่ไปยังโรงงานทั่วไปที่เกี่ยวข้องหรืออะไรก็ตามที่ต้องการ "คลาสทั่วไปที่มีวิธีการคงที่นี้"

ตัวอย่างเช่น:

class Factory<TProduct> where TProduct : new()
{
    public delegate void ProductInitializationMethod(TProduct newProduct);


    private ProductInitializationMethod m_ProductInitializationMethod;


    public Factory(ProductInitializationMethod p_ProductInitializationMethod)
    {
        m_ProductInitializationMethod = p_ProductInitializationMethod;
    }

    public TProduct CreateProduct()
    {
        var prod = new TProduct();
        m_ProductInitializationMethod(prod);
        return prod;
    }
}

class ProductA
{
    public static void InitializeProduct(ProductA newProduct)
    {
        // .. Do something with a new ProductA
    }
}

class ProductB
{
    public static void InitializeProduct(ProductB newProduct)
    {
        // .. Do something with a new ProductA
    }
}

class GenericAndDelegateTest
{
    public static void Main()
    {
        var factoryA = new Factory<ProductA>(ProductA.InitializeProduct);
        var factoryB = new Factory<ProductB>(ProductB.InitializeProduct);

        ProductA prodA = factoryA.CreateProduct();
        ProductB prodB = factoryB.CreateProduct();
    }
}

น่าเสียดายที่คุณไม่สามารถบังคับได้ว่าคลาสนั้นมีวิธีการที่ถูกต้อง แต่อย่างน้อยคุณก็สามารถคอมไพล์ไทม์ - บังคับได้ว่าเมธอดของโรงงานที่ได้นั้นมีทุกสิ่งที่คาดหวัง (เช่นวิธีการเริ่มต้นที่มีลายเซ็นที่ถูกต้อง) ซึ่งดีกว่าข้อยกเว้นการสะท้อนเวลาทำงาน

วิธีนี้ยังมีประโยชน์บางประการเช่นคุณสามารถนำวิธีการเริ่มต้นมาใช้ใหม่ให้เป็นวิธีการอินสแตนซ์เป็นต้น


1

คุณควรจะทำได้โดยใช้การสะท้อนตามที่อธิบายไว้ที่นี่

เนื่องจากลิงก์ถูกปิดฉันพบรายละเอียดที่เกี่ยวข้องในเครื่องย้อนกลับ:

สมมติว่าคุณมีคลาสที่มีวิธีการทั่วไปแบบคงที่:

class ClassWithGenericStaticMethod
{
    public static void PrintName<T>(string prefix) where T : class
    {
        Console.WriteLine(prefix + " " + typeof(T).FullName);
    }
}

คุณจะเรียกใช้วิธีนี้โดยใช้การเลือกใหม่ได้อย่างไร?

มันกลายเป็นเรื่องง่ายมาก ... นี่คือวิธีที่คุณเรียกใช้ Static Generic Method โดยใช้ Reflection:

// Grabbing the type that has the static generic method
Type typeofClassWithGenericStaticMethod = typeof(ClassWithGenericStaticMethod);

// Grabbing the specific static method
MethodInfo methodInfo = typeofClassWithGenericStaticMethod.GetMethod("PrintName", System.Reflection.BindingFlags.Static | BindingFlags.Public);

// Binding the method info to generic arguments
Type[] genericArguments = new Type[] { typeof(Program) };
MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(genericArguments);

// Simply invoking the method and passing parameters
// The null parameter is the object to call the method from. Since the method is
// static, pass null.
object returnValue = genericMethodInfo.Invoke(null, new object[] { "hello" });

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