Làm cách nào tôi có thể sử dụng giao diện như một ràng buộc loại chung C #?


164

Có cách nào để có được khai báo hàm sau không?

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

I E. Trong đó T là loại giao diện (tương tự where T : classstruct).

Hiện tại tôi đã giải quyết cho:

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

Trong đó IBase được định nghĩa là một giao diện trống được kế thừa bởi tất cả các giao diện tùy chỉnh của tôi ... Không lý tưởng, nhưng nó sẽ hoạt động ... Tại sao bạn không thể xác định rằng một loại chung phải là một giao diện?

Đối với những gì nó có giá trị, tôi muốn điều này bởi vì Foođang phản chiếu nơi nó cần một loại giao diện ... Tôi có thể chuyển nó thành một tham số bình thường và thực hiện kiểm tra cần thiết trong chính chức năng, nhưng điều này có vẻ an toàn hơn nhiều (và tôi giả sử hiệu suất cao hơn một chút, vì tất cả các kiểm tra được thực hiện tại compXLime).


4
Trên thực tế, IBase dea của bạn là tốt nhất tôi từng thấy cho đến nay. Thật không may, bạn không thể sử dụng nó cho các giao diện bạn không sở hữu. Tất cả C # sẽ phải làm là có tất cả các giao diện được kế thừa từ IOjbect giống như tất cả các lớp kế thừa từ Object.
Rhyous

1
Lưu ý: Điều này xảy ra là một ý tưởng khá phổ biến. Các giao diện trống như IBase- được sử dụng theo cách này - được gọi là giao diện đánh dấu . Chúng cho phép các hành vi đặc biệt cho các loại 'được đánh dấu'.
pius

Câu trả lời:


132

Cách gần nhất bạn có thể làm (ngoại trừ cách tiếp cận giao diện cơ sở) là " where T : class", nghĩa là kiểu tham chiếu. Không có cú pháp có nghĩa là "bất kỳ giao diện".

Điều này (" where T : class") được sử dụng, ví dụ, trong WCF để giới hạn khách hàng trong các hợp đồng dịch vụ (giao diện).


7
Câu trả lời hay, nhưng bạn có biết tại sao cú pháp này không tồn tại không? Có vẻ như nó sẽ là một tính năng dễ có.
Stephen Holt

@StephenHolt: Tôi nghĩ rằng những người tạo ra .NET, khi quyết định những ràng buộc nào cho phép, đã tập trung vào những thứ sẽ cho phép các lớp và phương thức chung làm những việc với các kiểu chung mà chúng không thể, thay vì ngăn chúng sử dụng những cách vô nghĩa. Điều đó đã được nói, một interfaceràng buộc về việc Tcho phép so sánh tham chiếu giữa Tvà bất kỳ loại tham chiếu nào khác, vì so sánh tham chiếu được cho phép giữa bất kỳ giao diện nào và hầu hết mọi loại tham chiếu khác, và cho phép so sánh ngay cả trường hợp đó sẽ không gây ra vấn đề gì.
supercat

1
@supercat một ứng dụng hữu ích khác của ràng buộc giả định như vậy sẽ là tạo một proxy một cách an toàn cho các thể hiện của loại. Đối với giao diện, nó được đảm bảo an toàn, trong khi đối với các lớp kín, nó sẽ thất bại, giống như đối với các lớp có phương thức không ảo.
Ivan Danilov

@IvanDanilov: Có một số ràng buộc có thể hiểu được, nếu được phép, sẽ hữu ích chặn một số cấu trúc vô nghĩa. Tôi đồng ý một ràng buộc cho "bất kỳ loại giao diện" nào cũng tốt, nhưng tôi không thấy rằng nó sẽ cho phép mọi thứ không thể được thực hiện mà không có nó, tiết kiệm cho việc tạo ra các chuỗi thời gian biên dịch khi các nỗ lực được thực hiện những thứ có thể thất bại trong thời gian chạy.
supercat

113

Tôi biết điều này hơi muộn nhưng đối với những người quan tâm bạn có thể sử dụng kiểm tra thời gian chạy.

typeof(T).IsInterface

11
+1 vì là câu trả lời duy nhất để chỉ ra điều này. Tôi chỉ thêm một câu trả lời với một cách tiếp cận để cải thiện hiệu suất bằng cách chỉ kiểm tra mỗi loại một lần thay vì mỗi lần phương thức được gọi.
phoog

9
Toàn bộ ý tưởng của thuốc generic trong C # là có sự an toàn về thời gian biên dịch. Những gì bạn đang đề xuất cũng có thể được thực hiện bằng một phương pháp không chung chung Foo(Type type).
Jacek Gorgoń

Tôi thích kiểm tra thời gian chạy. Cảm ơn.
Tarık zgün Güner

Ngoài ra trong thời gian chạy, bạn có thể sử dụng if (new T() is IMyInterface) { }để kiểm tra xem một giao diện có được thực hiện bởi lớp T không. Có thể không phải là hiệu quả nhất, nhưng nó hoạt động.
tkerwood

26

Không, thực sự, nếu bạn đang nghĩ classstructcó nghĩa là classes và structs, bạn đã sai. classcó nghĩa là bất kỳ loại tài liệu tham khảo (ví dụ bao gồm giao diện quá) và structcó nghĩa là bất kỳ kiểu giá trị (ví dụ struct, enum).


1
Không phải đó là định nghĩa về sự khác biệt giữa một lớp và một cấu trúc: rằng mỗi lớp là một loại tham chiếu (và ngược lại) và ditto cho các loại giá trị / giá trị
Matthew Scharley

Matthew: Có nhiều loại giá trị hơn các cấu trúc C #. Ví dụ, Enums là các loại giá trị và where T : structràng buộc khớp .
Mehrdad Afshari

Cần lưu ý rằng một loại giao diện được sử dụng trong các ràng buộc không ngụ ý class, nhưng khai báo vị trí lưu trữ của loại giao diện thực sự tuyên bố vị trí lưu trữ là tham chiếu lớp thực hiện loại đó.
supercat

4
Để có nhiều hơn chính xác, where T : structtương ứng với NotNullableValueTypeConstraint, vì vậy nó có nghĩa là nó phải là một kiểu giá trị khác hơn Nullable<>. (Vì vậy, Nullable<>một kiểu cấu trúc không thỏa mãn các where T : structràng buộc.)
Jeppe Stig Nielsen

19

Để theo dõi câu trả lời của Robert, điều này thậm chí còn muộn hơn, nhưng bạn có thể sử dụng lớp trình trợ giúp tĩnh để thực hiện kiểm tra thời gian chạy một lần duy nhất cho mỗi loại:

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

Tôi cũng lưu ý rằng giải pháp "nên làm việc" của bạn trên thực tế không hoạt động. Xem xét:

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

Bây giờ không có gì ngăn bạn gọi Foo như vậy:

Foo<Actual>();

Các Actuallớp, sau khi tất cả, đáp ứng các IBasehạn chế.


Một staticconstructor không thể public, vì vậy điều này sẽ gây ra lỗi thời gian biên dịch. Ngoài ra staticlớp của bạn có chứa một phương thức cá thể, đó cũng là một lỗi thời gian biên dịch.
Jeppe Stig Nielsen

Được tin là nhờ nawfal đã sửa các lỗi được ghi nhận bởi @JeppeStigNielsen
phoog

10

Lâu nay tôi đã nghĩ về những hạn chế thời gian gần biên dịch, vì vậy đây là một cơ hội hoàn hảo để khởi chạy khái niệm này.

Ý tưởng cơ bản là nếu bạn không thể thực hiện kiểm tra thời gian biên dịch, bạn nên thực hiện nó vào thời điểm sớm nhất có thể, về cơ bản là thời điểm ứng dụng bắt đầu. Nếu tất cả các kiểm tra đều ổn, ứng dụng sẽ chạy; nếu kiểm tra thất bại, ứng dụng sẽ thất bại ngay lập tức.

Hành vi

Kết quả tốt nhất có thể là chương trình của chúng tôi không biên dịch nếu các ràng buộc không được đáp ứng. Thật không may, điều đó là không thể trong triển khai C # hiện tại.

Điều tốt nhất tiếp theo là chương trình gặp sự cố ngay khi nó bắt đầu.

Tùy chọn cuối cùng là chương trình sẽ sập thời điểm mã được nhấn. Đây là hành vi mặc định của .NET. Đối với tôi, điều này là hoàn toàn không thể chấp nhận.

Điều kiện tiên quyết

Chúng ta cần phải có một cơ chế ràng buộc, vì vậy, nếu thiếu bất cứ điều gì tốt hơn ... hãy sử dụng một thuộc tính. Thuộc tính sẽ có mặt trên một ràng buộc chung để kiểm tra xem nó có phù hợp với điều kiện của chúng ta không. Nếu không, chúng tôi sẽ đưa ra một lỗi xấu.

Điều này cho phép chúng tôi làm những việc như thế này trong mã của chúng tôi:

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

(Tôi đã giữ where T:classở đây, vì tôi luôn thích kiểm tra thời gian biên dịch hơn kiểm tra thời gian chạy)

Vì vậy, điều đó chỉ khiến chúng tôi gặp 1 vấn đề, đó là kiểm tra xem tất cả các loại mà chúng tôi sử dụng có khớp với ràng buộc không. Nó có thể khó như thế nào?

Chúng ta hãy phá vỡ nó

Các kiểu chung luôn ở trên một lớp (/ struct / interface) hoặc trên một phương thức.

Kích hoạt một ràng buộc đòi hỏi bạn phải thực hiện một trong những điều sau đây:

  1. Biên dịch thời gian, khi sử dụng một loại trong một loại (kế thừa, ràng buộc chung, thành viên lớp)
  2. Biên dịch thời gian, khi sử dụng một kiểu trong thân phương thức
  3. Thời gian chạy, khi sử dụng sự phản chiếu để xây dựng một cái gì đó dựa trên lớp cơ sở chung.
  4. Thời gian chạy, khi sử dụng sự phản chiếu để xây dựng một cái gì đó dựa trên RTTI.

Tại thời điểm này, tôi muốn nói rằng bạn nên luôn luôn tránh làm (4) trong bất kỳ chương trình IMO nào. Bất kể, những kiểm tra này sẽ không hỗ trợ nó, vì nó thực sự có nghĩa là giải quyết vấn đề tạm dừng.

Trường hợp 1: sử dụng một loại

Thí dụ:

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

Ví dụ 2:

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

Về cơ bản, việc này bao gồm quét tất cả các loại, kế thừa, thành viên, tham số, v.v., v.v ... Nếu một loại là loại chung và có một ràng buộc, chúng tôi kiểm tra ràng buộc; nếu đó là một mảng, chúng tôi kiểm tra loại phần tử.

Tại thời điểm này tôi phải thêm rằng điều này sẽ phá vỡ sự thật rằng mặc định .NET tải các loại 'lười biếng'. Bằng cách quét tất cả các loại, chúng tôi buộc .NET runtime tải tất cả chúng. Đối với hầu hết các chương trình, điều này không phải là một vấn đề; Tuy nhiên, nếu bạn sử dụng các trình khởi tạo tĩnh trong mã của mình, bạn có thể gặp phải sự cố với cách tiếp cận này ... Điều đó nói rằng, tôi sẽ không khuyên ai làm điều này bằng mọi cách (ngoại trừ những điều như thế này :-), vì vậy nó không nên đưa ra bạn rất nhiều vấn đề

Trường hợp 2: sử dụng một loại trong một phương thức

Thí dụ:

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

Để kiểm tra điều này, chúng tôi chỉ có 1 tùy chọn: dịch ngược lớp, kiểm tra tất cả các mã thông báo thành viên được sử dụng và nếu một trong số chúng là loại chung - hãy kiểm tra các đối số.

Trường hợp 3: Phản xạ, xây dựng chung thời gian chạy

Thí dụ:

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

Tôi cho rằng về mặt lý thuyết có thể kiểm tra điều này bằng các thủ thuật tương tự như trường hợp (2), nhưng việc thực hiện nó khó hơn nhiều (bạn cần kiểm tra xem có MakeGenericTypeđược gọi trong một số đường dẫn mã không). Tôi sẽ không đi vào chi tiết ở đây ...

Trường hợp 4: Phản xạ, RTTI thời gian chạy

Thí dụ:

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

Đây là trường hợp xấu nhất và như tôi đã giải thích trước đây nói chung là một ý tưởng tồi IMHO. Dù bằng cách nào, không có cách thực tế nào để tìm ra điều này bằng cách sử dụng séc.

Kiểm tra lô

Tạo một chương trình kiểm tra trường hợp (1) và (2) sẽ dẫn đến kết quả như sau:

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

Sử dụng mã

Chà, đó là phần dễ dàng :-)

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

Bạn không thể làm điều này trong bất kỳ phiên bản phát hành nào của C #, cũng như trong C # 4.0 sắp tới. Đây cũng không phải là giới hạn C # - không có ràng buộc "giao diện" trong chính CLR.


6

Nếu có thể, tôi đã đi với một giải pháp như thế này. Nó chỉ hoạt động nếu bạn muốn một số giao diện cụ thể (ví dụ: những giao diện bạn có quyền truy cập nguồn) được chuyển qua dưới dạng tham số chung, không phải bất kỳ giao diện nào.

  • Tôi để các giao diện của mình, có vấn đề, kế thừa một giao diện trống IInterface.
  • Tôi ràng buộc tham số T chung là IInterface

Trong nguồn, nó trông như thế này:

  • Bất kỳ giao diện nào bạn muốn được chuyển qua làm tham số chung:

    public interface IWhatever : IInterface
    {
        // IWhatever specific declarations
    }
  • Mặt trước:

    public interface IInterface
    {
        // Nothing in here, keep moving
    }
  • Lớp mà bạn muốn đặt ràng buộc kiểu:

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

Điều này không thực hiện được nhiều. Bạn Tkhông bị ràng buộc với các giao diện, nó bị ràng buộc với bất kỳ thứ gì thực hiện IInterface- điều mà bất kỳ loại nào cũng có thể làm nếu nó muốn, ví dụ như struct Foo : IInterfacevì bạn IInterfacerất có thể là công khai (nếu không mọi thứ chấp nhận nó sẽ phải là nội bộ).
AnorZaken

Nếu bạn kiểm soát tất cả các loại bạn muốn chấp nhận, thì bạn có thể sử dụng tạo mã để tạo tất cả các tình trạng quá tải phù hợp, tất cả chỉ chuyển hướng đến một phương thức riêng tư chung.
AnorZaken

2

Những gì bạn đã giải quyết là tốt nhất bạn có thể làm:

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

2

Tôi đã cố gắng làm một cái gì đó tương tự và sử dụng một giải pháp khắc phục: Tôi đã nghĩ về toán tử ngầm và rõ ràng về cấu trúc: Ý tưởng là bọc Kiểu trong một cấu trúc có thể được chuyển đổi thành Kiểu ngầm.

Đây là một cấu trúc như vậy:

public struct InterfaceType {private Type _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));
}

}

sử dụng cơ bản:

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

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

Bạn phải tưởng tượng mecanism của riêng bạn xung quanh điều này, nhưng một ví dụ có thể là một phương thức lấy tham số InterfaceType trong tham số thay vì một kiểu

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

Một phương thức ghi đè sẽ trả về các loại giao diện:

public virtual IEnumerable<InterfaceType> GetInterfaces()

Có thể có những điều liên quan đến thuốc generic, nhưng tôi đã không thử

Hy vọng điều này có thể giúp hoặc đưa ra ý tưởng :-)


0

Giải pháp A: Sự kết hợp các ràng buộc này sẽ đảm bảo đó TInterfacelà một giao diện:

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

Nó đòi hỏi một cấu trúc duy nhất TStructnhư một Nhân chứng để chứng minh đó TInterfacelà một cấu trúc.

Bạn có thể sử dụng cấu trúc đơn làm nhân chứng cho tất cả các loại không chung chung của mình:

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

Giải pháp B: Nếu bạn không muốn thực hiện cấu trúc như nhân chứng, bạn có thể tạo giao diện

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

và sử dụng một ràng buộc:

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

Triển khai cho các giao diện:

interface IA :ISInterface<IA>{ }

Điều này giải quyết một số vấn đề, nhưng đòi hỏi sự tin tưởng rằng không ai thực hiện ISInterface<T>cho các loại không có giao diện, nhưng điều đó khá khó thực hiện.


-4

Sử dụng một lớp trừu tượng thay thế. Vì vậy, bạn sẽ có một cái gì đó như:

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

10
Bạn không thể luôn thay thế một giao diện bằng một lớp trừu tượng vì C # không hỗ trợ nhiều kế thừa.
Sam
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.