मैं 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 डिव ने मुझे अब तक देखा सबसे अच्छा है। दुर्भाग्य से, आप इसका उपयोग उन इंटरफेस के लिए नहीं कर सकते हैं जो आपके पास नहीं हैं। सभी C # को करना होगा IOjbect से विरासत में प्राप्त सभी इंटरफेस ठीक उसी तरह हैं जैसे ऑब्जेक्ट से प्राप्त सभी वर्ग।
रिहायस

1
नोट: यह एक सामान्य विचार है। खाली इंटरफेस जैसे IBase- इस तरह से उपयोग किया जाता है - मार्कर इंटरफेस कहा जाता है । वे 'चिह्नित' प्रकारों के लिए विशेष व्यवहार सक्षम करते हैं।
पायस

जवाबों:


132

निकटतम आप कर सकते हैं (अपने बेस-इंटरफ़ेस दृष्टिकोण को छोड़कर) " where T : class" है, जिसका अर्थ संदर्भ-प्रकार है। "किसी भी इंटरफ़ेस" का अर्थ करने के लिए कोई वाक्यविन्यास नहीं है।

यह (" where T : class") प्रयोग किया जाता है, उदाहरण के लिए, WCF में ग्राहकों को सेवा अनुबंधों (इंटरफेस) तक सीमित करने के लिए।


7
अच्छा जवाब, लेकिन क्या आपके पास कोई विचार है कि यह वाक्यविन्यास मौजूद क्यों नहीं है? लगता है कि यह एक अच्छी सुविधा होगी।
स्टीफन होल्ट

@StephenHolt: मुझे लगता है कि .NET के निर्माता यह सोचते हैं कि अनुमति देने में क्या अड़चनें हैं, उन पर ध्यान केंद्रित किया गया, जो सामान्य कक्षाओं और विधियों को सामान्य प्रकार के साथ काम करने देंगे जो वे अन्यथा नहीं कर सकते थे, बजाय इसके कि उनका उपयोग करने से रोका जाए। निरर्थक तरीके। कहा गया है कि, एक interfaceबाधा के Tबीच संदर्भ तुलना Tऔर किसी भी अन्य संदर्भ प्रकार की अनुमति चाहिए , क्योंकि संदर्भ तुलना किसी भी इंटरफ़ेस और लगभग किसी भी अन्य संदर्भ प्रकार के बीच की अनुमति दी जाती है, और तुलना की अनुमति देता है कि मामले में कोई समस्या नहीं होगी।
सुपरकैट

1
@ इस तरह के काल्पनिक बाधा का एक और उपयोगी अनुप्रयोग सुरक्षित रूप से प्रकार के उदाहरणों के लिए एक प्रॉक्सी बनाना होगा। इंटरफ़ेस के लिए यह सुरक्षित होने की गारंटी है, जबकि सीलबंद कक्षाओं के लिए यह विफल होगा, गैर-आभासी तरीकों के साथ कक्षाओं के लिए भी।
इवान

@ इवानदानिलोव: कई बोधगम्य बाधाएं हैं, अगर अनुमति दी जाए, तो उपयोगी रूप से कुछ निरर्थक निर्माणों को अवरुद्ध करेगा। मैं मानता हूं कि "कोई भी इंटरफ़ेस प्रकार" के लिए एक बाधा अच्छा होगा, लेकिन मैं यह नहीं देखता कि यह किसी भी चीज के लिए अनुमति देगा जो इसके बिना नहीं किया जा सकता है, संकलन-समय के स्क्वाकों की पीढ़ी के लिए बचत करें जब करने का प्रयास किया जाता है। ऐसी चीज़ें जो अन्यथा रनटाइम में विफल हो सकती हैं।
20

113

मुझे पता है कि यह थोड़ा देर से है लेकिन उन लोगों के लिए जो आपकी रुचि रखते हैं आप रनटाइम चेक का उपयोग कर सकते हैं।

typeof(T).IsInterface

11
इसे इंगित करने के लिए एकमात्र उत्तर होने के लिए +1। मैंने सिर्फ हर बार विधि को कॉल करने के बजाय प्रत्येक प्रकार की जाँच करके प्रदर्शन में सुधार करने के दृष्टिकोण के साथ एक उत्तर जोड़ा।
फोग

9
C # में जेनरिक का पूरा विचार संकलन-समय सुरक्षा के लिए है। आप जो सुझाव दे रहे हैं, वह गैर-जेनेरिक पद्धति से किया जा सकता है Foo(Type type)
जसेक गोर्गो

मुझे रनटाइम चेक पसंद है। धन्यवाद।
तारिक gzgün Güner

इसके अलावा रन समय में आप यह if (new T() is IMyInterface) { }जांचने के लिए उपयोग कर सकते हैं कि क्या कोई इंटरफ़ेस T वर्ग द्वारा कार्यान्वित किया गया है। सबसे कुशल नहीं हो सकता है, लेकिन यह काम करता है।
tkerwood

26

नहीं, वास्तव में, यदि आप सोच रहे हैं classऔर एस और एस का structमतलब है , तो आप गलत हैं। इसका मतलब है किसी भी संदर्भ प्रकार (उदाहरण के लिए इंटरफेस भी शामिल है) और इसका मतलब है किसी भी मान प्रकार (उदाहरण के लिए , )।classstructclassstructstructenum


1
ऐसा नहीं है कि एक वर्ग और एक संरचना के बीच अंतर की परिभाषा: हालांकि कि हर वर्ग एक संदर्भ प्रकार (और इसके विपरीत) और स्टक्ट / मूल्य प्रकार के लिए डिट्टो है
मैथ्यू शारले

मैथ्यू: सी # स्ट्रक्चर्स के मुकाबले वैल्यू टाइप्स ज्यादा हैं। उदाहरण के लिए एनम, मूल्य प्रकार और मैच where T : structबाधा हैं।
मेहरदाद अफश्री

यह ध्यान देने योग्य है कि बाधाओं में उपयोग किया जाने वाला एक इंटरफ़ेस प्रकार का अर्थ नहीं है class, लेकिन एक इंटरफ़ेस प्रकार के भंडारण स्थान की घोषणा वास्तव में भंडारण स्थान को एक वर्ग संदर्भ होने की घोषणा करता है जो उस प्रकार को लागू करता है।
सुपरकैट

4
और भी अधिक सटीक, होना करने के लिए where T : structसे मेल खाती है NotNullableValueTypeConstraint, तो इसका मतलब यह एक मान प्रकार होना चाहिए अन्य की तुलना में Nullable<>। (तो Nullable<>एक संरचनात्मक प्रकार है जो where T : structबाधा को संतुष्ट नहीं करता है ।)
जेपी स्टिग नीलसन

19

रॉबर्ट के जवाब का पालन करने के लिए, यह बाद में भी है, लेकिन आप केवल एक बार रनटाइम चेक करने के लिए एक स्थिर सहायक वर्ग का उपयोग कर सकते हैं:

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

Actualवर्ग, सभी को संतुष्ट करता है के बाद, IBaseबाधा।


एक staticनिर्माता नहीं हो सकता है public, इसलिए इसे एक संकलन-समय त्रुटि देना चाहिए। साथ ही आपके staticवर्ग में एक इंस्टेंस विधि है, जो एक संकलन-समय की त्रुटि भी है।
जेपी स्टिग नील्सन

@JeppeStigNielsen
phoog

10

कुछ समय से मैं निकट-संकलन-समय की बाधाओं के बारे में सोच रहा हूं, इसलिए यह अवधारणा को लॉन्च करने का एक सही मौका है।

मूल विचार यह है कि यदि आप एक चेक संकलित समय नहीं कर सकते हैं, तो आपको इसे जल्द से जल्द संभव बिंदु पर करना चाहिए, जो मूल रूप से वह क्षण है जब आवेदन शुरू होता है। यदि सभी जांच ठीक हैं, तो आवेदन चलेगा; यदि कोई चेक विफल हो जाता है, तो एप्लिकेशन तुरंत विफल हो जाएगा।

व्यवहार

सबसे अच्छा संभव परिणाम यह है कि अगर बाधाएं पूरी नहीं हुईं तो हमारा कार्यक्रम संकलित नहीं करता है। दुर्भाग्य से वर्तमान सी # कार्यान्वयन में यह संभव नहीं है।

अगली सबसे अच्छी बात यह है कि यह कार्यक्रम उस पल को क्रैश कर देता है जो शुरू हुआ है।

अंतिम विकल्प यह है कि प्रोग्राम उस क्षण को क्रैश कर देगा जब कोड हिट होता है। यह .NET का डिफ़ॉल्ट व्यवहार है। मेरे लिए, यह पूरी तरह से अस्वीकार्य है।

पूर्व आवश्यकताएं

हमें एक बाधा तंत्र की आवश्यकता है, इसलिए कुछ भी बेहतर की कमी के लिए ... चलो एक विशेषता का उपयोग करें। यह देखने के लिए कि क्या यह हमारी शर्तों से मेल खाता है, यह विशेषता एक सामान्य बाधा के ऊपर मौजूद होगी। यदि ऐसा नहीं होता है, तो हम एक बदसूरत त्रुटि देते हैं।

यह हमारे कोड में इस तरह की चीजें करने में सक्षम बनाता है:

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

(मैंने where T:classयहां रखा है, क्योंकि मैं हमेशा रन-टाइम चेक करने के लिए संकलन-समय की जांच पसंद करता हूं)

इसलिए, यह केवल हमें 1 समस्या के साथ छोड़ देता है, जो यह जांच कर रहा है कि सभी प्रकार के जो हम उपयोग करते हैं वे बाधा से मेल खाते हैं। यह कितना सख्त हो सकता है?

चलो इसे तोड़ो

जेनेरिक प्रकार हमेशा एक वर्ग (/ संरचना / इंटरफ़ेस) या एक विधि पर होते हैं।

एक बाधा को ट्रिगर करने के लिए आपको निम्नलिखित चीजों में से एक करना होगा:

  1. संकलन-समय, जब एक प्रकार में एक प्रकार का उपयोग कर (वंशानुक्रम, सामान्य बाधा, कक्षा सदस्य)
  2. संकलन-समय, जब एक विधि निकाय में एक प्रकार का उपयोग कर
  3. रन-टाइम, जब सामान्य बेस क्लास के आधार पर कुछ का निर्माण करने के लिए प्रतिबिंब का उपयोग किया जाता है।
  4. रन-टाइम, जब आरटीटीआई के आधार पर कुछ बनाने के लिए प्रतिबिंब का उपयोग किया जाता है।

इस बिंदु पर, मैं यह बताना चाहूंगा कि आपको किसी भी कार्यक्रम IMO में करने से हमेशा बचना चाहिए। इसके बावजूद, ये चेक इसका समर्थन नहीं करेंगे, क्योंकि यह प्रभावी रूप से हल करने की समस्या को हल करने का मतलब होगा।

केस 1: एक प्रकार का उपयोग करके

उदाहरण:

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

उदाहरण 2:

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

मूल रूप से इसमें सभी प्रकार, वंशानुक्रम, सदस्य, पैरामीटर, आदि, आदि को स्कैन करना शामिल है। यदि एक प्रकार एक सामान्य प्रकार है और एक बाधा है, तो हम बाधा की जांच करते हैं; यदि यह एक सरणी है, तो हम तत्व प्रकार की जांच करते हैं।

इस बिंदु पर मुझे यह जोड़ना होगा कि यह इस तथ्य को तोड़ देगा कि डिफ़ॉल्ट रूप से .NET 'आलसी' प्रकार का लोड करता है। सभी प्रकारों को स्कैन करके, हम .NET रनटाइम को उन सभी को लोड करने के लिए मजबूर करते हैं। अधिकांश कार्यक्रमों के लिए यह एक समस्या नहीं होनी चाहिए; फिर भी, यदि आप अपने कोड में स्थिर इनिशियलाइज़र का उपयोग करते हैं, तो आप इस दृष्टिकोण के साथ समस्याओं का सामना कर सकते हैं ... उन्होंने कहा, मैं किसी को भी इस तरह से करने की सलाह नहीं दूंगा (इस तरह की चीजों को छोड़कर), इसलिए इसे नहीं देना चाहिए आपको बहुत सारी समस्याएं हैं।

केस 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 में। यह सी # सीमा नहीं है, या तो - सीएलआर में कोई "इंटरफ़ेस" बाधा नहीं है।


6

यदि संभव हो तो, मैं इस तरह से एक समाधान के साथ चला गया। यह केवल तभी काम करता है जब आप कई विशिष्ट इंटरफेस चाहते हैं (जैसे जिनके पास आपके पास स्रोत है) को एक सामान्य पैरामीटर के रूप में पारित किया जाना चाहिए, न कि किसी भी।

  • मैंने अपना इंटरफेस दिया, जो प्रश्न में आया था, एक खाली इंटरफ़ेस विरासत में मिला IInterface
  • मैंने जेनेरिक टी पैरामीटर के लिए विवश किया 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

यदि आप सभी प्रकार को नियंत्रित करना चाहते हैं जिसे आप वैसे भी स्वीकार करना चाहते हैं, तो आप सभी उपयुक्त ओवरलोड्स बनाने के लिए कोड-पीढ़ी का उपयोग कर सकते हैं, जिनमें से सभी एक सामान्य निजी पद्धति पर पुनर्निर्देशित करते हैं।
एनोरजेन

2

आपने जो कुछ किया है, वह सबसे अच्छा है जो आप कर सकते हैं:

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

2

मैंने ऐसा ही कुछ करने की कोशिश की और वर्कअराउंड सॉल्यूशन का उपयोग किया: मैंने संरचना पर निहित और स्पष्ट ऑपरेटर के बारे में सोचा: विचार यह है कि टाइप को एक संरचना में लपेटना है जिसे टाइप इनफैक्टली में बदला जा सकता है।

यहाँ ऐसी संरचना है:

सार्वजनिक संरचना इंटरफ़ेस टाइप {निजी प्रकार _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();
    //...
}

समाधान बी: ​​यदि आप गवाह के रूप में संरचना नहीं बनाना चाहते हैं तो आप एक इंटरफ़ेस बना सकते हैं

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 # कई उत्तराधिकार का समर्थन नहीं करता है।
सैम
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.