Arabirimi C # genel tür kısıtı olarak nasıl kullanabilirim?


164

Aşağıdaki işlev bildirimini almanın bir yolu var mı?

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

yani. burada T (benzer bir arabirimi olan where T : classve struct).

Şu anda yerleştim:

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

IBase, tüm özel arayüzlerim tarafından devralınan boş bir arayüz olarak tanımlandığında ... İdeal değil, ama işe yarayacak ... Neden genel bir türün bir arayüz olması gerektiğini tanımlayamıyorsunuz?

Değeri için, bunu istiyorum çünkü Foobir arayüz tipine ihtiyaç duyduğu yerde yansıma yapıyor ... Normal bir parametre olarak geçebilirim ve fonksiyonun kendisinde gerekli kontrolü yapabilirim, ama bu çok daha tipte görünüyordu (ve ben varsayalım ki, tüm kontroller derleme sırasında yapıldığından, biraz daha performans sergileyin).


4
Aslında, IBase dea'nız şimdiye kadar gördüğüm en iyisi. Maalesef, sahip olmadığınız arayüzler için kullanamazsınız. Tüm C # 'ın yapması gereken, tüm sınıfların tıpkı bütün Object nesnesinden miras aldığı gibi IOjbect'den miras alınmasıdır.
Rhyous

1
Not: Bu oldukça yaygın bir fikirdir. IBaseBu şekilde kullanılan boş arabirimlere marker arabirimleri denir . 'İşaretli' tipler için özel davranışlar sağlarlar.
pius

Yanıtlar:


132

Yapabileceğiniz en yakın şey (temel arayüz yaklaşımınız hariç) " where T : class", referans türü anlamına gelir. "Herhangi bir arayüz" anlamına gelecek bir sözdizimi yoktur.

Bu (" where T : class"), örneğin WCF'de istemcileri hizmet sözleşmeleriyle (arabirimler) sınırlamak için kullanılır.


7
güzel bir yanıt, ancak bu sözdiziminin neden mevcut olmadığına dair bir fikrin var mı? Görünüşe göre güzel bir özellik olurdu.
Stephen Holt

@StephenHolt: Sanırım .NET yaratıcıları, hangi kısıtlamalara izin verileceğine karar verirken, genel sınıfların ve yöntemlerin, kullanılmalarını önlemek yerine, genel türlerle yapamayacakları şeyleri yapmalarına izin verecek olanlara odaklandıklarını düşünüyorum. saçma sapan yollar. Bununla birlikte, interfaceüzerinde herhangi bir arabirim ve hemen hemen tüm diğer referans türleri arasında referans karşılaştırmalara izin verildiğinden ve bu durumda bile Tkarşılaştırma yapılmasına izin verilmediğinden , üzerinde bir kısıtlama, Tdiğer herhangi bir referans türü arasında referans karşılaştırmalarına izin vermelidir .
supercat

1
@supercat böyle bir varsayımsal kısıtlamanın başka bir yararlı uygulaması, türün örnekleri için güvenli bir proxy oluşturmak olacaktır. Arayüz için güvenli olduğu garanti edilirken, kapalı sınıflar için sanal olmayan yöntemlere sahip sınıflarla aynı şekilde başarısız olur.
Ivan Danilov

@IvanDanilov: İzin verilirse bazı saçma yapıları yararlı bir şekilde engelleyen bir dizi akla yatkın kısıtlama var. "Herhangi bir arabirim türü" için bir kısıtlamanın iyi olacağını kabul ediyorum, ancak onsuz yapılamayan herhangi bir şeye izin vereceğini görmüyorum, denemeler yapıldığında derleme zamanı squawks nesli için tasarruf edin çalışma zamanında başarısız olabilecek şeyler.
supercat

113

Bu biraz geç biliyorum ama ilgilenenler için bir çalışma zamanı denetimi kullanabilirsiniz.

typeof(T).IsInterface

11
Bunu işaret eden tek cevap olduğu için +1. Yöntemin her çağrılması yerine her bir türü yalnızca bir kez kontrol ederek performansı artırmak için bir yaklaşımla bir cevap ekledim.
phoog

9
C # 'daki jeneriklerin tümü derleme zamanı güvenliğine sahip olmaktır. Önerdiğiniz şey, genel olmayan bir yöntemle de gerçekleştirilebilir Foo(Type type).
Jacek Gorgoń

Çalışma zamanı kontrolünü seviyorum. Teşekkürler.
Tarık Özgün Güner

Ayrıca çalışma zamanında if (new T() is IMyInterface) { }T sınıfı bir arabirimin uygulanıp uygulanmadığını kontrol etmek için kullanabilirsiniz . En verimli olmayabilir, ama işe yarıyor.
tkerwood

26

Hayýr, aslýnda, düţünüyor classve es ve s structdemek istiyorsan, yanýlýyorsun. anlamına gelir herhangi bir referans tipi (örneğin çok arabirimler içerir) ve anlamı , herhangi bir değer türü (örneğin , ).classstructclassstructstructenum


1
Bir sınıf ve bir yapı arasındaki farkın tanımı bu değildir: her sınıfın bir referans türü (ve tersi) ve davranış / değer türleri için ditto olması
Matthew Scharley

Matthew: Değer türleri için C # yapılarından daha fazlası vardır. Örneğin numaralandırmalar değer türleri ve eşleşme where T : structkısıtlamasıdır.
Mehrdad Afshari

Kısıtlamalarda kullanılan bir arabirim türünün ima etmediğini belirtmek gerekir class, ancak bir arabirim türünün depolama konumunu bildirmek, depolama konumunu gerçekten bu türü uygulayan bir sınıf başvurusu olarak bildirir.
supercat

4
Daha kesin olmak için, where T : structkarşılık için NotNullableValueTypeConstraint, bu yüzden bir değer türü olması gerektiği anlamına gelmektedir , diğer daha Nullable<>. ( Kısıtlamayı Nullable<>karşılamayan bir yapı tipi de öyle where T : struct.)
Jeppe Stig Nielsen

19

Robert'ın cevabını takip etmek için, bu daha da geç, ancak çalışma zamanını yalnızca tür başına bir kez kontrol etmek için statik bir yardımcı sınıf kullanabilirsiniz:

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() { /*...*/ }
}

Ayrıca "çalışmalı" çözümünüzün aslında işe yaramadığını da not ediyorum. Düşünmek:

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

Şimdi Foo'yu aramanızı engelleyen hiçbir şey yok:

Foo<Actual>();

ActualSınıf, tatmin, sonuçta IBasekısıt.


Bir staticyapıcı olamaz public, bu nedenle bu derleme zamanı hatası vermelidir. Ayrıca staticsınıfınız bir örnekleme yöntemi içerir, bu da derleme zamanı hatasıdır.
Jeppe Stig Nielsen

@JeppeStigNielsen tarafından kaydedilen hataları düzelttiği için nawfal'a teşekkürler
phoog

10

Bir süredir derleme zamanı kısıtlamaları üzerinde duruyordum, bu yüzden bu konsepti başlatmak için mükemmel bir fırsat.

Temel fikir, bir derleme zamanı kontrol edemezseniz, bunu mümkün olan en erken zamanda yapmanız gerekir, bu da temel olarak uygulamanın başladığı andır. Tüm kontroller uygunsa uygulama çalışır; bir kontrol başarısız olursa, uygulama anında başarısız olur.

davranış

Olası en iyi sonuç, kısıtlamalar karşılanmadığı takdirde programımızın derlenmemesidir. Ne yazık ki mevcut C # uygulamasında bu mümkün değil.

Bir sonraki en iyi şey, programın başlatıldığı anda çökmesidir.

Son seçenek, kod vurulduğu anda programın çökmesidir. Bu, .NET'in varsayılan davranışıdır. Benim için bu tamamen kabul edilemez.

Ön Gereksinimler

Bir kısıtlama mekanizmasına ihtiyacımız var, bu yüzden daha iyi bir şey olmaması için ... bir özellik kullanalım. Özellik, koşullarımızla eşleşip eşleşmediğini kontrol etmek için genel bir kısıtlamanın üstünde bulunur. Değilse, çirkin bir hata veriyoruz.

Bu, kodumuzda böyle şeyler yapmamızı sağlar:

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

( where T:classBurada kaldım , çünkü her zaman derleme zamanı denetimlerini çalışma zamanı denetimlerine tercih ediyorum)

Yani, bu sadece bize 1 problem bırakıyor, bu da kullandığımız tüm tiplerin kısıtlamaya uyup uymadığını kontrol ediyor. Ne kadar zor olabilir?

Hadi parçalayalım

Genel türler her zaman ya bir sınıfta (/ struct / interface) ya da bir yöntem üzerindedir.

Bir kısıtlamayı tetiklemek için aşağıdakilerden birini yapmanız gerekir:

  1. Bir türdeki bir türü kullanırken derleme zamanı (devralma, genel kısıtlama, sınıf üyesi)
  2. Bir yöntem gövdesinde bir tür kullanırken derleme zamanı
  3. Genel taban sınıfına dayalı bir şey oluşturmak için yansıma kullanılırken çalışma zamanı.
  4. RTTI tabanlı bir şey oluşturmak için yansıma kullanırken çalışma zamanı.

Bu noktada, herhangi bir program IMO'sunda her zaman (4) yapmaktan kaçınmanız gerektiğini belirtmek isterim. Ne olursa olsun, bu kontroller onu desteklemeyecektir, çünkü bu durma problemini etkili bir şekilde çözmek anlamına gelir.

Durum 1: bir tür kullanma

Misal:

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

Örnek 2:

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

Temel olarak bu, tüm türlerin, kalıtımın, üyelerin, parametrelerin vb. Vb. Taranmasını içerir. Bir tür genel bir türse ve bir kısıtlaması varsa, kısıtlamayı kontrol ederiz; bir dizi ise, öğe türünü kontrol ederiz.

Bu noktada, bu varsayılan olarak .NET 'türleri' tembel 'yükler gerçeğini kıracağını eklemek gerekir. Tüm türleri tarayarak .NET çalışma zamanını hepsini yüklemeye zorlarız. Çoğu program için bu bir problem olmamalı; yine de, kodunuzda statik başlatıcılar kullanırsanız, bu yaklaşımla ilgili sorunlarla karşılaşabilirsiniz ... Yani, bunu yapması için kimseye tavsiye etmem (bu gibi şeyler hariç :-), bu yüzden vermemelisiniz) bir sürü problem var.

Durum 2: Bir yöntemde bir tür kullanma

Misal:

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

Bunu kontrol etmek için sadece 1 seçeneğimiz var: sınıfı kodalayın, kullanılan tüm üye belirteçlerini kontrol edin ve bunlardan biri genel türse - bağımsız değişkenleri kontrol edin.

Durum 3: Yansıma, çalışma zamanı genel yapısı

Misal:

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

Teorik olarak bu (2) gibi benzer hileler ile kontrol etmek mümkündür, ancak uygulanması çok daha zordur ( MakeGenericTypebazı kod yolunda denir olup olmadığını kontrol etmeniz gerekir ). Burada ayrıntılara girmeyeceğim ...

Durum 4: Yansıma, çalışma zamanı RTTI

Misal:

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

Bu en kötü durum senaryosudur ve daha önce açıkladığım gibi IMHO genellikle kötü bir fikirdir. Her iki durumda da, çekleri kullanarak bunu anlamanın pratik bir yolu yoktur.

Parti testi

Vaka (1) ve (2) 'yi test eden bir program oluşturmak şu şekilde sonuçlanır:

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

Kodu kullanma

Peki, bu kolay kısım :-)

// 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

Bunu herhangi bir C # sürümünde ya da yaklaşan C # 4.0 sürümünde yapamazsınız. Bu bir C # sınırlaması da değildir - CLR'nin kendisinde "arabirim" kısıtlaması yoktur.


6

Mümkünse böyle bir çözüm buldum. Yalnızca birkaç belirli arabirimin (örneğin, kaynak erişiminiz olanların) genel bir parametre olarak geçirilmesini istiyorsanız çalışır;

  • Söz konusu arayüzlerimin boş bir arayüz devralmasına izin verdim IInterface.
  • Genel T parametresini IInterface

Kaynakta şöyle görünür:

  • Genel parametre olarak iletilmek istediğiniz herhangi bir arabirim:

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

    public interface IInterface
    {
        // Nothing in here, keep moving
    }
  • Tür kısıtlaması koymak istediğiniz sınıf:

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

Bu pek bir şey yapamaz. Sizin Tarabirimlerinizle sınırlı değil, uygulayan herhangi bir şeyle sınırlıdır IInterface- istediği takdirde herhangi bir türün yapabileceği, örneğin struct Foo : IInterfacesizin IInterfacebüyük olasılıkla halka açık olduğu için (aksi takdirde bunu kabul eden her şey dahili olması gerekir).
AnorZaken

Yine de kabul etmek istediğiniz tüm türleri kontrol ederseniz, hepsi genel bir özel yönteme yönlendiren tüm uygun aşırı yüklemeleri oluşturmak için kod oluşturma kullanabilirsiniz.
AnorZaken

2

Yerleştirdiğiniz şey yapabileceğiniz en iyisidir:

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

2

Ben benzer bir şey yapmaya çalıştım ve geçici bir çözüm kullandım: Yapısal örtülü ve açık operatör düşündüm: Fikir türü örtük tür dönüştürülebilir bir yapıda sarmak için.

İşte böyle bir yapı:

public struct InterfaceType {özel Tür _türü;

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

}

temel kullanım:

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

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

Bunun etrafında kendi mekanizmanız olduğunu hayal etmelisiniz, ancak bir örnek, bir tür yerine parametrede bir InterfaceType alınan bir yöntem olabilir

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

Arabirim türlerini döndürmesi gereken geçersiz kılma yöntemi:

public virtual IEnumerable<InterfaceType> GetInterfaces()

Jeneriklerle de ilgili bir şeyler olabilir ama denemedim

Umarım bu yardımcı olabilir veya fikir verebilir :-)


0

Çözüm A: Bu kısıtlama kombinasyonu TInterface, bir arayüz olduğunu garanti etmelidir :

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

Bunun bir yapı TStructolduğunu kanıtlamak için Şahit olarak tek TInterfacebir yapı gerekir.

Genel olmayan tüm türleriniz için tanık olarak tek bir yapı kullanabilirsiniz:

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

Çözüm B: Tanık olarak yapı yapmak istemiyorsanız bir arayüz oluşturabilirsiniz

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

ve bir kısıtlama kullanın:

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

Arayüzler için uygulama:

interface IA :ISInterface<IA>{ }

Bu, bazı sorunları çözer, ancak hiç kimsenin ISInterface<T>arayüz dışı tipler için uygulanmadığına dair güven gerektirir , ancak yanlışlıkla yapılması oldukça zordur.


-4

Bunun yerine soyut bir sınıf kullanın. Yani, şöyle bir şey olurdu:

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

10
C # birden çok devralmayı desteklemediğinden, arabirimi her zaman soyut bir sınıfla değiştiremezsiniz.
Sam
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.