ฉันจะใช้ส่วนต่อประสานเป็นข้อ จำกัด ประเภท C # ทั่วไปได้อย่างไร


164

มีวิธีรับฟังก์ชั่นการประกาศต่อไปนี้หรือไม่?

public bool Foo<T>() where T : interface;

กล่าวคือ โดยที่ T เป็นประเภทอินเตอร์เฟส (คล้ายกับwhere T : classและstruct)

ขณะนี้ฉันได้ตัดสิน:

public bool Foo<T>() where T : IBase;

ที่ IBase ถูกกำหนดให้เป็นอินเตอร์เฟสว่างที่สืบทอดโดยอินเตอร์เฟสแบบกำหนดเองทั้งหมดของฉัน ... ไม่เหมาะ แต่ควรใช้งานได้ ... ทำไมคุณไม่สามารถกำหนดประเภททั่วไปต้องเป็นส่วนติดต่อได้

สำหรับสิ่งที่คุ้มค่าฉันต้องการสิ่งนี้เพราะFooทำสิ่งที่ต้องการอินเทอร์เฟซชนิด ... ฉันสามารถส่งมันเป็นพารามิเตอร์ปกติและทำการตรวจสอบที่จำเป็นในฟังก์ชั่นของตัวเอง สมมติว่ามีประสิทธิภาพมากกว่านี้เล็กน้อยเนื่องจากการตรวจสอบทั้งหมดเสร็จสิ้นในเวลารวบรวม)


4
ที่จริงแล้ว IBase dea ของคุณดีที่สุดเท่าที่ฉันเคยเห็นมา น่าเสียดายที่คุณไม่สามารถใช้งานได้สำหรับส่วนต่อประสานที่คุณไม่ได้เป็นเจ้าของ สิ่งที่ C # ทุกคนต้องทำคือมีส่วนต่อประสานที่สืบทอดมาจาก IOjbect เช่นเดียวกับคลาสทั้งหมดที่สืบทอดมาจาก Object
Rhyous

1
หมายเหตุ:นี่เป็นความคิดที่ค่อนข้างธรรมดา อินเตอร์เฟซที่ว่างเปล่าเช่นIBase- ใช้วิธีนี้ - จะเรียกว่าอินเตอร์เฟซเครื่องหมาย พวกเขาเปิดใช้งานพฤติกรรมพิเศษสำหรับประเภท 'ทำเครื่องหมาย'
pius

คำตอบ:


132

สิ่งที่ใกล้เคียงที่สุดที่คุณสามารถทำได้ (ยกเว้นสำหรับวิธีการเชื่อมต่อฐานของคุณ) คือ " where T : class", หมายถึงประเภทการอ้างอิง ไม่มีไวยากรณ์ที่หมายถึง "อินเตอร์เฟสใด ๆ "

ตัวอย่างนี้ใช้ (" where T : class") ใน WCF เพื่อ จำกัด ลูกค้าไปยังสัญญาบริการ (อินเทอร์เฟซ)


7
คำตอบที่ดี แต่คุณมีความคิดว่าทำไมไวยากรณ์นี้ไม่อยู่? ดูเหมือนว่ามันจะเป็นคุณสมบัติที่ดีที่จะมี
สตีเฟ่นโฮลต์

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

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

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

113

ฉันรู้ว่ามันค่อนข้างช้า แต่สำหรับผู้ที่สนใจคุณสามารถใช้การตรวจสอบรันไทม์

typeof(T).IsInterface

11
+1 สำหรับการเป็นคำตอบเดียวที่ชี้ประเด็นนี้ ฉันเพิ่งเพิ่มคำตอบด้วยวิธีการในการปรับปรุงประสิทธิภาพโดยการตรวจสอบแต่ละประเภทเพียงครั้งเดียวมากกว่าทุกครั้งที่เรียกว่าวิธีการ
phoog

9
ความคิดทั้งหมดของ generics ใน C # คือการมีความปลอดภัยเวลารวบรวม Foo(Type type)สิ่งที่คุณกำลังบอกก็สามารถรวมทั้งจะดำเนินการด้วยวิธีการที่ไม่ได้ทั่วไป
Jacek Gorgoń

ฉันชอบการตรวจสอบรันไทม์ ขอบคุณ
TarıkÖzgünGüner

นอกจากนี้ ณ รันไทม์คุณสามารถใช้if (new T() is IMyInterface) { }เพื่อตรวจสอบว่าอินเทอร์เฟซถูกใช้งานโดยคลาส T หรือไม่ อาจไม่ได้มีประสิทธิภาพมากที่สุด แต่ก็ใช้งานได้
tkerwood

26

ไม่จริงถ้าคุณคิดclassและstructหมายถึงclasses และstructs คุณผิด classหมายถึงประเภทอ้างอิงใด ๆ (เช่นรวมถึงอินเทอร์เฟซด้วย) และstructหมายถึงประเภทค่าใด ๆ (เช่นstruct, enum)


1
ไม่ว่าคำนิยามของความแตกต่างระหว่างระดับและ struct แม้ว่า: นั่นทุกระดับเป็นชนิดอ้างอิง (และในทางกลับกัน) และเหมือนกันชนิด stuct / ค่า
แมทธิว Scharley

Matthew: มีประเภทของค่ามากกว่า C # structs ตัวอย่างเช่น Enums เป็นประเภทค่าและwhere T : structข้อ จำกัด การจับคู่
Mehrdad Afshari

เป็นที่น่าสังเกตว่าประเภทอินเตอร์เฟสที่ใช้ในข้อ จำกัด นั้นไม่ได้บอกเป็นนัยclassแต่การประกาศตำแหน่งที่เก็บข้อมูลของประเภทอินเตอร์เฟสนั้นจริง ๆ แล้วประกาศว่าที่เก็บข้อมูลนั้นเป็นที่อ้างอิงระดับที่ใช้ประเภทนั้น
supercat

4
การจะมีมากยิ่งขึ้นได้อย่างแม่นยำwhere T : structสอดคล้องกับNotNullableValueTypeConstraintดังนั้นจึงหมายความว่ามันจะต้องเป็นประเภทค่าอื่น ๆNullable<>กว่า ( Nullable<>เป็นประเภท struct ที่ไม่เป็นไปตามwhere T : structข้อ จำกัด )
Jeppe Stig Nielsen

19

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

public bool Foo<T>() where T : class
{
    FooHelper<T>.Foo();
}

private static class FooHelper<TInterface> where TInterface : class
{
    static FooHelper()
    {
        if (!typeof(TInterface).IsInterface)
            throw // ... some exception
    }
    public static void Foo() { /*...*/ }
}

ฉันยังทราบด้วยว่าโซลูชัน "ควรทำงาน" ของคุณไม่ได้ทำงานได้จริง พิจารณา:

public bool Foo<T>() where T : IBase;
public interface IBase { }
public interface IActual : IBase { string S { get; } }
public class Actual : IActual { public string S { get; set; } }

ตอนนี้ไม่มีอะไรหยุดคุณจากการเรียก Foo ดังนี้:

Foo<Actual>();

Actualระดับหลังจากทั้งหมดพึงพอใจIBaseจำกัด


ตัวstaticสร้างไม่สามารถเป็นได้publicดังนั้นควรให้ข้อผิดพลาดเวลาคอมไพล์ นอกจากนี้staticคลาสของคุณมีวิธีการอินสแตนซ์นั่นเป็นข้อผิดพลาดในการคอมไพล์เช่นกัน
Jeppe Stig Nielsen

ล่าช้าแล้วขอบคุณ nawfal สำหรับการแก้ไขข้อผิดพลาดที่บันทึกไว้โดย @JeppeStigNielsen
phoog

10

บางครั้งตอนนี้ฉันกำลังคิดถึงข้อ จำกัด ที่ใกล้เคียงกันดังนั้นนี่เป็นโอกาสที่สมบูรณ์แบบสำหรับการเปิดตัวแนวคิด

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

พฤติกรรม

ผลลัพธ์ที่ดีที่สุดที่เป็นไปได้คือโปรแกรมของเราไม่ได้รวบรวมหากข้อ จำกัด ไม่เป็นไปตาม น่าเสียดายที่ไม่สามารถใช้ C # ในปัจจุบันได้

สิ่งที่ดีที่สุดถัดไปคือโปรแกรมหยุดทำงานทันทีที่เริ่มทำงาน

ตัวเลือกสุดท้ายคือโปรแกรมจะหยุดทำงานทันทีที่มีการกดรหัส นี่คือพฤติกรรมเริ่มต้นของ. NET สำหรับฉันมันไม่สามารถยอมรับได้อย่างสมบูรณ์

Prerequirements

เราจำเป็นต้องมีกลไกที่มีข้อ จำกัด ดังนั้นหากขาดสิ่งใดที่ดีกว่า ... มาใช้แอตทริบิวต์กันเถอะ แอตทริบิวต์นี้จะปรากฏบนข้อ จำกัด ทั่วไปเพื่อตรวจสอบว่าตรงกับเงื่อนไขของเราหรือไม่ หากไม่เป็นเช่นนั้นเราจะให้ข้อผิดพลาดที่น่าเกลียด

สิ่งนี้ทำให้เราสามารถทำสิ่งนี้ในรหัสของเรา:

public class Clas<[IsInterface] T> where T : class

(ฉันเก็บไว้ที่where T:classนี่เพราะฉันชอบตรวจสอบคอมไพล์เวลามากกว่าเช็ครันไทม์)

ดังนั้นนั่นทำให้เรามีเพียง 1 ปัญหาเท่านั้นซึ่งกำลังตรวจสอบว่าประเภททั้งหมดที่เราใช้ตรงกับข้อ จำกัด หรือไม่ มันยากเหลือเกิน

มาทำลายมันกันเถอะ

ประเภททั่วไปมักจะอยู่ในคลาส (/ struct / interface) หรือวิธีการ

การเรียกใช้ข้อ จำกัด คุณต้องทำสิ่งใดสิ่งหนึ่งต่อไปนี้:

  1. เวลารวบรวมเมื่อใช้ชนิดในชนิด (การสืบทอดข้อ จำกัด ทั่วไปสมาชิกคลาส)
  2. รวบรวมเวลาเมื่อใช้ประเภทในร่างกายวิธีการ
  3. เวลาทำงานเมื่อใช้การสะท้อนเพื่อสร้างสิ่งที่ยึดตามคลาสพื้นฐานทั่วไป
  4. เวลาทำงานเมื่อใช้การสะท้อนเพื่อสร้างบางสิ่งตาม RTTI

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

กรณีที่ 1: ใช้ชนิด

ตัวอย่าง:

public class TestClass : SomeClass<IMyInterface> { ... } 

ตัวอย่างที่ 2:

public class TestClass 
{ 
    SomeClass<IMyInterface> myMember; // or a property, method, etc.
} 

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

ณ จุดนี้ฉันต้องเพิ่มว่านี้จะทำลายความจริงที่ว่าโดยค่าเริ่มต้น. NET ประเภทโหลด 'ขี้เกียจ' โดยการสแกนทุกประเภทเราบังคับให้รันไทม์. NET ในการโหลดทั้งหมด สำหรับโปรแกรมส่วนใหญ่สิ่งนี้ไม่ควรมีปัญหา ยังถ้าคุณใช้ initializers คงที่ในรหัสของคุณคุณอาจพบปัญหากับวิธีการนี้ ... ที่กล่าวว่าฉันจะไม่แนะนำให้ใครทำเช่นนี้ (ยกเว้นสิ่งเช่นนี้ :-) ดังนั้นจึงไม่ควรให้ คุณมีปัญหามากมาย

กรณีที่ 2: ใช้ชนิดในเมธอด

ตัวอย่าง:

void Test() {
    new SomeClass<ISomeInterface>();
}

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

กรณีที่ 3: การสะท้อนการสร้างทั่วไปแบบรันไทม์

ตัวอย่าง:

typeof(CtorTest<>).MakeGenericType(typeof(IMyInterface))

ฉันคิดว่ามันเป็นไปได้ในทางทฤษฎีที่จะตรวจสอบเรื่องนี้ด้วยเทคนิคที่คล้ายกันเป็นกรณี (2) แต่การดำเนินการของมันเป็นยากมาก (คุณต้องตรวจสอบว่าMakeGenericTypeจะเรียกว่าอยู่ในเส้นทางโค้ดบางส่วน) ฉันจะไม่ลงรายละเอียดที่นี่ ...

กรณีที่ 4: การสะท้อน, รันไทม์ RTTI

ตัวอย่าง:

Type t = Type.GetType("CtorTest`1[IMyInterface]");

นี่เป็นสถานการณ์กรณีที่เลวร้ายที่สุดและตามที่ฉันอธิบายมาก่อนโดยทั่วไปแล้วความคิดที่ไม่ดี IMHO ไม่ว่าจะใช้วิธีใดในการคำนวณโดยใช้เช็ค

ทดสอบมาก

การสร้างโปรแกรมที่ทดสอบเคส (1) และ (2) จะส่งผลดังนี้:

[AttributeUsage(AttributeTargets.GenericParameter)]
public class IsInterface : ConstraintAttribute
{
    public override bool Check(Type genericType)
    {
        return genericType.IsInterface;
    }

    public override string ToString()
    {
        return "Generic type is not an interface";
    }
}

public abstract class ConstraintAttribute : Attribute
{
    public ConstraintAttribute() {}

    public abstract bool Check(Type generic);
}

internal class BigEndianByteReader
{
    public BigEndianByteReader(byte[] data)
    {
        this.data = data;
        this.position = 0;
    }

    private byte[] data;
    private int position;

    public int Position
    {
        get { return position; }
    }

    public bool Eof
    {
        get { return position >= data.Length; }
    }

    public sbyte ReadSByte()
    {
        return (sbyte)data[position++];
    }

    public byte ReadByte()
    {
        return (byte)data[position++];
    }

    public int ReadInt16()
    {
        return ((data[position++] | (data[position++] << 8)));
    }

    public ushort ReadUInt16()
    {
        return (ushort)((data[position++] | (data[position++] << 8)));
    }

    public int ReadInt32()
    {
        return (((data[position++] | (data[position++] << 8)) | (data[position++] << 0x10)) | (data[position++] << 0x18));
    }

    public ulong ReadInt64()
    {
        return (ulong)(((data[position++] | (data[position++] << 8)) | (data[position++] << 0x10)) | (data[position++] << 0x18) | 
                        (data[position++] << 0x20) | (data[position++] << 0x28) | (data[position++] << 0x30) | (data[position++] << 0x38));
    }

    public double ReadDouble()
    {
        var result = BitConverter.ToDouble(data, position);
        position += 8;
        return result;
    }

    public float ReadSingle()
    {
        var result = BitConverter.ToSingle(data, position);
        position += 4;
        return result;
    }
}

internal class ILDecompiler
{
    static ILDecompiler()
    {
        // Initialize our cheat tables
        singleByteOpcodes = new OpCode[0x100];
        multiByteOpcodes = new OpCode[0x100];

        FieldInfo[] infoArray1 = typeof(OpCodes).GetFields();
        for (int num1 = 0; num1 < infoArray1.Length; num1++)
        {
            FieldInfo info1 = infoArray1[num1];
            if (info1.FieldType == typeof(OpCode))
            {
                OpCode code1 = (OpCode)info1.GetValue(null);
                ushort num2 = (ushort)code1.Value;
                if (num2 < 0x100)
                {
                    singleByteOpcodes[(int)num2] = code1;
                }
                else
                {
                    if ((num2 & 0xff00) != 0xfe00)
                    {
                        throw new Exception("Invalid opcode: " + num2.ToString());
                    }
                    multiByteOpcodes[num2 & 0xff] = code1;
                }
            }
        }
    }

    private ILDecompiler() { }

    private static OpCode[] singleByteOpcodes;
    private static OpCode[] multiByteOpcodes;

    public static IEnumerable<ILInstruction> Decompile(MethodBase mi, byte[] ildata)
    {
        Module module = mi.Module;

        BigEndianByteReader reader = new BigEndianByteReader(ildata);
        while (!reader.Eof)
        {
            OpCode code = OpCodes.Nop;

            int offset = reader.Position;
            ushort b = reader.ReadByte();
            if (b != 0xfe)
            {
                code = singleByteOpcodes[b];
            }
            else
            {
                b = reader.ReadByte();
                code = multiByteOpcodes[b];
                b |= (ushort)(0xfe00);
            }

            object operand = null;
            switch (code.OperandType)
            {
                case OperandType.InlineBrTarget:
                    operand = reader.ReadInt32() + reader.Position;
                    break;
                case OperandType.InlineField:
                    if (mi is ConstructorInfo)
                    {
                        operand = module.ResolveField(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), Type.EmptyTypes);
                    }
                    else
                    {
                        operand = module.ResolveField(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments());
                    }
                    break;
                case OperandType.InlineI:
                    operand = reader.ReadInt32();
                    break;
                case OperandType.InlineI8:
                    operand = reader.ReadInt64();
                    break;
                case OperandType.InlineMethod:
                    try
                    {
                        if (mi is ConstructorInfo)
                        {
                            operand = module.ResolveMember(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), Type.EmptyTypes);
                        }
                        else
                        {
                            operand = module.ResolveMember(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments());
                        }
                    }
                    catch
                    {
                        operand = null;
                    }
                    break;
                case OperandType.InlineNone:
                    break;
                case OperandType.InlineR:
                    operand = reader.ReadDouble();
                    break;
                case OperandType.InlineSig:
                    operand = module.ResolveSignature(reader.ReadInt32());
                    break;
                case OperandType.InlineString:
                    operand = module.ResolveString(reader.ReadInt32());
                    break;
                case OperandType.InlineSwitch:
                    int count = reader.ReadInt32();
                    int[] targetOffsets = new int[count];
                    for (int i = 0; i < count; ++i)
                    {
                        targetOffsets[i] = reader.ReadInt32();
                    }
                    int pos = reader.Position;
                    for (int i = 0; i < count; ++i)
                    {
                        targetOffsets[i] += pos;
                    }
                    operand = targetOffsets;
                    break;
                case OperandType.InlineTok:
                case OperandType.InlineType:
                    try
                    {
                        if (mi is ConstructorInfo)
                        {
                            operand = module.ResolveMember(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), Type.EmptyTypes);
                        }
                        else
                        {
                            operand = module.ResolveMember(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments());
                        }
                    }
                    catch
                    {
                        operand = null;
                    }
                    break;
                case OperandType.InlineVar:
                    operand = reader.ReadUInt16();
                    break;
                case OperandType.ShortInlineBrTarget:
                    operand = reader.ReadSByte() + reader.Position;
                    break;
                case OperandType.ShortInlineI:
                    operand = reader.ReadSByte();
                    break;
                case OperandType.ShortInlineR:
                    operand = reader.ReadSingle();
                    break;
                case OperandType.ShortInlineVar:
                    operand = reader.ReadByte();
                    break;

                default:
                    throw new Exception("Unknown instruction operand; cannot continue. Operand type: " + code.OperandType);
            }

            yield return new ILInstruction(offset, code, operand);
        }
    }
}

public class ILInstruction
{
    public ILInstruction(int offset, OpCode code, object operand)
    {
        this.Offset = offset;
        this.Code = code;
        this.Operand = operand;
    }

    public int Offset { get; private set; }
    public OpCode Code { get; private set; }
    public object Operand { get; private set; }
}

public class IncorrectConstraintException : Exception
{
    public IncorrectConstraintException(string msg, params object[] arg) : base(string.Format(msg, arg)) { }
}

public class ConstraintFailedException : Exception
{
    public ConstraintFailedException(string msg) : base(msg) { }
    public ConstraintFailedException(string msg, params object[] arg) : base(string.Format(msg, arg)) { }
}

public class NCTChecks
{
    public NCTChecks(Type startpoint)
        : this(startpoint.Assembly)
    { }

    public NCTChecks(params Assembly[] ass)
    {
        foreach (var assembly in ass)
        {
            assemblies.Add(assembly);

            foreach (var type in assembly.GetTypes())
            {
                EnsureType(type);
            }
        }

        while (typesToCheck.Count > 0)
        {
            var t = typesToCheck.Pop();
            GatherTypesFrom(t);

            PerformRuntimeCheck(t);
        }
    }

    private HashSet<Assembly> assemblies = new HashSet<Assembly>();

    private Stack<Type> typesToCheck = new Stack<Type>();
    private HashSet<Type> typesKnown = new HashSet<Type>();

    private void EnsureType(Type t)
    {
        // Don't check for assembly here; we can pass f.ex. System.Lazy<Our.T<MyClass>>
        if (t != null && !t.IsGenericTypeDefinition && typesKnown.Add(t))
        {
            typesToCheck.Push(t);

            if (t.IsGenericType)
            {
                foreach (var par in t.GetGenericArguments())
                {
                    EnsureType(par);
                }
            }

            if (t.IsArray)
            {
                EnsureType(t.GetElementType());
            }
        }

    }

    private void PerformRuntimeCheck(Type t)
    {
        if (t.IsGenericType && !t.IsGenericTypeDefinition)
        {
            // Only check the assemblies we explicitly asked for:
            if (this.assemblies.Contains(t.Assembly))
            {
                // Gather the generics data:
                var def = t.GetGenericTypeDefinition();
                var par = def.GetGenericArguments();
                var args = t.GetGenericArguments();

                // Perform checks:
                for (int i = 0; i < args.Length; ++i)
                {
                    foreach (var check in par[i].GetCustomAttributes(typeof(ConstraintAttribute), true).Cast<ConstraintAttribute>())
                    {
                        if (!check.Check(args[i]))
                        {
                            string error = "Runtime type check failed for type " + t.ToString() + ": " + check.ToString();

                            Debugger.Break();
                            throw new ConstraintFailedException(error);
                        }
                    }
                }
            }
        }
    }

    // Phase 1: all types that are referenced in some way
    private void GatherTypesFrom(Type t)
    {
        EnsureType(t.BaseType);

        foreach (var intf in t.GetInterfaces())
        {
            EnsureType(intf);
        }

        foreach (var nested in t.GetNestedTypes())
        {
            EnsureType(nested);
        }

        var all = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance;
        foreach (var field in t.GetFields(all))
        {
            EnsureType(field.FieldType);
        }
        foreach (var property in t.GetProperties(all))
        {
            EnsureType(property.PropertyType);
        }
        foreach (var evt in t.GetEvents(all))
        {
            EnsureType(evt.EventHandlerType);
        }
        foreach (var ctor in t.GetConstructors(all))
        {
            foreach (var par in ctor.GetParameters())
            {
                EnsureType(par.ParameterType);
            }

            // Phase 2: all types that are used in a body
            GatherTypesFrom(ctor);
        }
        foreach (var method in t.GetMethods(all))
        {
            if (method.ReturnType != typeof(void))
            {
                EnsureType(method.ReturnType);
            }

            foreach (var par in method.GetParameters())
            {
                EnsureType(par.ParameterType);
            }

            // Phase 2: all types that are used in a body
            GatherTypesFrom(method);
        }
    }

    private void GatherTypesFrom(MethodBase method)
    {
        if (this.assemblies.Contains(method.DeclaringType.Assembly)) // only consider methods we've build ourselves
        {
            MethodBody methodBody = method.GetMethodBody();
            if (methodBody != null)
            {
                // Handle local variables
                foreach (var local in methodBody.LocalVariables)
                {
                    EnsureType(local.LocalType);
                }

                // Handle method body
                var il = methodBody.GetILAsByteArray();
                if (il != null)
                {
                    foreach (var oper in ILDecompiler.Decompile(method, il))
                    {
                        if (oper.Operand is MemberInfo)
                        {
                            foreach (var type in HandleMember((MemberInfo)oper.Operand))
                            {
                                EnsureType(type);
                            }

                        }
                    }
                }
            }
        }
    }

    private static IEnumerable<Type> HandleMember(MemberInfo info)
    {
        // Event, Field, Method, Constructor or Property.
        yield return info.DeclaringType;
        if (info is EventInfo)
        {
            yield return ((EventInfo)info).EventHandlerType;
        }
        else if (info is FieldInfo)
        {
            yield return ((FieldInfo)info).FieldType;
        }
        else if (info is PropertyInfo)
        {
            yield return ((PropertyInfo)info).PropertyType;
        }
        else if (info is ConstructorInfo)
        {
            foreach (var par in ((ConstructorInfo)info).GetParameters())
            {
                yield return par.ParameterType;
            }
        }
        else if (info is MethodInfo)
        {
            foreach (var par in ((MethodInfo)info).GetParameters())
            {
                yield return par.ParameterType;
            }
        }
        else if (info is Type)
        {
            yield return (Type)info;
        }
        else
        {
            throw new NotSupportedException("Incorrect unsupported member type: " + info.GetType().Name);
        }
    }
}

ใช้รหัส

นั่นคือส่วนที่ง่าย :-)

// Create something illegal
public class Bar2 : IMyInterface
{
    public void Execute()
    {
        throw new NotImplementedException();
    }
}

// Our fancy check
public class Foo<[IsInterface] T>
{
}

class Program
{
    static Program()
    {
        // Perform all runtime checks
        new NCTChecks(typeof(Program));
    }

    static void Main(string[] args)
    {
        // Normal operation
        Console.WriteLine("Foo");
        Console.ReadLine();
    }
}

8

คุณไม่สามารถทำได้ใน C # รุ่นที่วางจำหน่ายหรือใน C # 4.0 ที่กำลังจะมาถึง ไม่ใช่ข้อ จำกัด ของ C # เช่นกัน - ไม่มีข้อ จำกัด "ส่วนต่อประสาน" ใน CLR


6

ถ้าเป็นไปได้ฉันไปแก้ปัญหาแบบนี้ ใช้งานได้เฉพาะในกรณีที่คุณต้องการให้อินเทอร์เฟซเฉพาะหลายรายการ (เช่นที่คุณมีการเข้าถึงแหล่งข้อมูล) ถูกส่งผ่านเป็นพารามิเตอร์ทั่วไป

  • IInterfaceฉันปล่อยให้อินเตอร์เฟซของฉันซึ่งเข้ามาในคำถามสืบทอดอินเตอร์เฟซที่ว่างเปล่า
  • ฉัน จำกัด พารามิเตอร์ T ทั่วไปให้เป็น IInterface

ในแหล่งที่มาดูเหมือนว่านี้:

  • อินเตอร์เฟสใด ๆ ที่คุณต้องการให้ส่งผ่านเป็นพารามิเตอร์ทั่วไป:

    public interface IWhatever : IInterface
    {
        // IWhatever specific declarations
    }
  • IInterface:

    public interface IInterface
    {
        // Nothing in here, keep moving
    }
  • คลาสที่คุณต้องการใส่ข้อ จำกัด ประเภท:

    public class WorldPeaceGenerator<T> where T : IInterface
    {
        // Actual world peace generating code
    }

มันไม่ได้สำเร็จมากนัก คุณTไม่ได้ถูก จำกัด ให้ใช้อินเทอร์เฟซมันถูก จำกัด ด้วยสิ่งใดก็ตามที่ดำเนินการIInterface- ซึ่งชนิดใดที่สามารถทำได้ถ้ามันต้องการเช่นstruct Foo : IInterfaceเนื่องจากคุณIInterfaceมีแนวโน้มที่จะเป็นสาธารณะมากที่สุด
AnorZaken

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


2

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

นี่คือโครงสร้าง:

InterfaceType สาธารณะสาธารณะ {ประเภทส่วนตัว _type;

public InterfaceType(Type type)
{
    CheckType(type);
    _type = type;
}

public static explicit operator Type(InterfaceType value)
{
    return value._type;
}

public static implicit operator InterfaceType(Type type)
{
    return new InterfaceType(type);
}

private static void CheckType(Type type)
{
    if (type == null) throw new NullReferenceException("The type cannot be null");
    if (!type.IsInterface) throw new NotSupportedException(string.Format("The given type {0} is not an interface, thus is not supported", type.Name));
}

}

การใช้งานพื้นฐาน:

// OK
InterfaceType type1 = typeof(System.ComponentModel.INotifyPropertyChanged);

// Throws an exception
InterfaceType type2 = typeof(WeakReference);

คุณต้องลองนึกภาพ mecanism ของคุณเองในสิ่งนี้ แต่ตัวอย่างอาจเป็นวิธีที่ใช้ InterfaceType ในพารามิเตอร์แทนที่จะเป็นชนิด

this.MyMethod(typeof(IMyType)) // works
this.MyMethod(typeof(MyType)) // throws exception

วิธีการแทนที่ที่ควรคืนค่าประเภทอินเตอร์เฟส:

public virtual IEnumerable<InterfaceType> GetInterfaces()

อาจมีสิ่งที่จะทำอย่างไรกับยาชื่อสามัญเช่นกัน แต่ฉันไม่ได้ลอง

หวังว่าสิ่งนี้สามารถช่วยหรือให้ความคิด :-)


0

โซลูชัน A: การรวมข้อ จำกัด นี้ควรรับประกันว่าTInterfaceเป็นส่วนต่อประสาน:

class example<TInterface, TStruct>
    where TStruct : struct, TInterface
    where TInterface : class
{ }

มันต้องมีโครงสร้างเดียวTStructในฐานะพยานเพื่อพิสูจน์ว่าTInterfaceเป็นโครงสร้าง

คุณสามารถใช้โครงสร้างเดียวเป็นพยานสำหรับประเภทที่ไม่ใช่ทั่วไปทั้งหมดของคุณ:

struct InterfaceWitness : IA, IB, IC 
{
    public int DoA() => throw new InvalidOperationException();
    //...
}

โซลูชัน B: หากคุณไม่ต้องการสร้าง struct เป็นพยานคุณสามารถสร้างส่วนต่อประสานได้

interface ISInterface<T>
    where T : ISInterface<T>
{ }

และใช้ข้อ จำกัด :

class example<TInterface>
    where TInterface : ISInterface<TInterface>
{ }

การใช้งานสำหรับอินเตอร์เฟส:

interface IA :ISInterface<IA>{ }

วิธีนี้ช่วยแก้ปัญหาบางอย่าง แต่ต้องการความเชื่อมั่นว่าไม่มีใครใช้งานISInterface<T>ประเภทที่ไม่ใช่อินเตอร์เฟส แต่มันค่อนข้างยากที่จะทำโดยไม่ตั้งใจ


-4

ใช้คลาสนามธรรมแทน ดังนั้นคุณจะมีสิ่งที่ชอบ:

public bool Foo<T>() where T : CBase;

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