Прочитать на английском

Поделиться через


Как: исследование и создание экземпляров универсальных типов с рефлексией

Сведения о универсальных типах получаются так же, как и сведения о других типах: путем изучения объекта Type, представляющего универсальный тип. Основное различие заключается в том, что универсальный тип содержит список объектов Type, представляющих его параметры универсального типа. Первая процедура в этом разделе проверяет универсальные типы.

Можно создать объект Type, представляющий собой составной тип, привязав аргументы типов к параметрам универсального типа. Вторая процедура демонстрирует это.

Исследовать универсальный тип и его параметры типа

  1. Получите экземпляр Type, который представляет универсальный тип. В следующем коде тип получается с помощью оператора C# typeof (GetType в Visual Basic). Другие способы получения Type объекта см. в разделе Type. В остальной части этой процедуры тип содержится в параметре метода с именем t.

    C#
    Type d1 = typeof(Dictionary<,>);
    
  2. Используйте свойство IsGenericType, чтобы определить, является ли тип универсальным, и используйте свойство IsGenericTypeDefinition, чтобы определить, является ли тип определением универсального типа.

    C#
    Console.WriteLine($"   Is this a generic type? {t.IsGenericType}");
    Console.WriteLine($"   Is this a generic type definition? {t.IsGenericTypeDefinition}");
    
  3. Получите массив, содержащий аргументы универсального типа, используя метод GetGenericArguments.

    C#
    Type[] typeParameters = t.GetGenericArguments();
    
  4. Для каждого аргумента типа определите, является ли он параметром типа (например, в определении универсального типа) или типом, указанным для параметра типа (например, в созданном типе), используя свойство IsGenericParameter.

    C#
    Console.WriteLine($"   List {typeParameters.Length} type arguments:");
    foreach (Type tParam in typeParameters)
    {
        if (tParam.IsGenericParameter)
        {
            DisplayGenericParameter(tParam);
        }
        else
        {
            Console.WriteLine($"      Type argument: {tParam}");
        }
    }
    
  5. В системе типов параметр универсального типа представлен экземпляром Typeтак же, как обычные типы. В следующем коде отображается позиция имени и параметра объекта Type, представляющего параметр универсального типа. Позиция параметра — это тривиальная информация здесь; Это более интересно при изучении параметра типа, который использовался в качестве аргумента типа другого универсального типа.

    C#
    Console.WriteLine($"      Type parameter: {tp.Name} position {tp.GenericParameterPosition}");
    
  6. Определите ограничение базового типа и ограничения интерфейса параметра универсального типа с помощью метода GetGenericParameterConstraints для получения всех ограничений в одном массиве. Ограничения не гарантируются в какой-либо определённой последовательности.

    C#
    foreach (Type iConstraint in tp.GetGenericParameterConstraints())
    {
        if (iConstraint.IsInterface)
        {
            Console.WriteLine($"         Interface constraint: {iConstraint}");
        }
    }
    
    Console.WriteLine($"         Base type constraint: {tp.BaseType ?? tp.BaseType: None}");
    
  7. Используйте свойство GenericParameterAttributes, чтобы обнаружить специальные ограничения для параметра типа, например требование, чтобы он был ссылочным типом. Свойство также содержит значения, представляющие дисперсию, которую можно скрыть, как показано в следующем коде.

    C#
    GenericParameterAttributes sConstraints =
        tp.GenericParameterAttributes &
        GenericParameterAttributes.SpecialConstraintMask;
    
  8. Атрибуты специальных ограничений — это флаги, а также один и тот же флаг (GenericParameterAttributes.None), который не представляет никаких специальных ограничений, также не представляет ковариации или контравариации. Таким образом, чтобы проверить любое из этих условий, необходимо использовать соответствующую маску. В этом случае используйте GenericParameterAttributes.SpecialConstraintMask для выделения флагов специальных ограничений.

    C#
    if (sConstraints == GenericParameterAttributes.None)
    {
        Console.WriteLine("         No special constraints.");
    }
    else
    {
        if (GenericParameterAttributes.None != (sConstraints &
            GenericParameterAttributes.DefaultConstructorConstraint))
        {
            Console.WriteLine("         Must have a parameterless constructor.");
        }
        if (GenericParameterAttributes.None != (sConstraints &
            GenericParameterAttributes.ReferenceTypeConstraint))
        {
            Console.WriteLine("         Must be a reference type.");
        }
        if (GenericParameterAttributes.None != (sConstraints &
            GenericParameterAttributes.NotNullableValueTypeConstraint))
        {
            Console.WriteLine("         Must be a non-nullable value type.");
        }
    }
    

Создание экземпляра универсального типа

Универсальный тип похож на шаблон. Вы не можете создавать экземпляры, если только вы не указываете реальные типы для его параметров универсального типа. Для этого во время выполнения, используя отражение, требуется метод MakeGenericType.

  1. Получите объект Type, представляющий универсальный тип. Следующий код получает универсальный тип Dictionary<TKey,TValue> двумя способами: с помощью перегрузки метода Type.GetType(String) со строкой, описывающей тип, и путем вызова метода GetGenericTypeDefinition для созданного типа Dictionary\<String, Example> (Dictionary(Of String, Example) в Visual Basic). Для метода MakeGenericType требуется определение универсального типа.

    C#
    // Use the typeof operator to create the generic type
    // definition directly. To specify the generic type definition,
    // omit the type arguments but retain the comma that separates
    // them.
    Type d1 = typeof(Dictionary<,>);
    
    // You can also obtain the generic type definition from a
    // constructed class. In this case, the constructed class
    // is a dictionary of Example objects, with String keys.
    Dictionary<string, Example> d2 = [];
    // Get a Type object that represents the constructed type,
    // and from that get the generic type definition. The
    // variables d1 and d4 contain the same type.
    Type d3 = d2.GetType();
    Type d4 = d3.GetGenericTypeDefinition();
    
  2. Создайте массив аргументов типа для замены параметров типа. Массив должен содержать правильное количество объектов Type в том же порядке, что и в списке параметров типа. В этом случае ключ (параметр первого типа) имеет тип String, а значения в словаре — это экземпляры класса с именем Example.

    C#
    Type[] typeArgs = [typeof(string), typeof(Example)];
    
  3. Вызовите метод MakeGenericType, чтобы привязать аргументы типа к параметрам типа и создать тип.

    C#
    Type constructed = d1.MakeGenericType(typeArgs);
    
  4. Используйте перегрузку метода CreateInstance(Type) для создания объекта сконструированного типа. Следующий код хранит два экземпляра класса Example в результирующем объекте Dictionary<String, Example>.

    C#
    _ = Activator.CreateInstance(constructed);
    

Пример

В следующем примере кода определяется метод DisplayGenericType для изучения определений универсальных типов и созданных типов, используемых в коде, и отображения их информации. Метод DisplayGenericType показывает, как использовать свойства IsGenericType, IsGenericParameterи GenericParameterPosition свойства и метод GetGenericArguments.

В примере также определяется метод DisplayGenericParameter для проверки параметра универсального типа и отображения ограничений.

В примере кода определяется набор типов тестов, включая универсальный тип, который иллюстрирует ограничения параметров типа, и показывает, как отображать сведения об этих типах.

В примере создается тип из класса Dictionary<TKey,TValue> путем создания массива аргументов типов и вызова метода MakeGenericType. Программа сравнивает объект Type, созданный с помощью MakeGenericType с объектом Type, полученным с помощью typeof (GetType в Visual Basic), демонстрируя, что они одинаковы. Аналогичным образом программа использует метод GetGenericTypeDefinition для получения определения универсального типа созданного типа и сравнивает его с объектом Type, представляющим класс Dictionary<TKey,TValue>.

C#
using System.Reflection;

// Define an example interface.
public interface ITestArgument { }

// Define an example base class.
public class TestBase { }

// Define a generic class with one parameter. The parameter
// has three constraints: It must inherit TestBase, it must
// implement ITestArgument, and it must have a parameterless
// constructor.
public class Test<T> where T : TestBase, ITestArgument, new() { }

// Define a class that meets the constraints on the type
// parameter of class Test.
public class TestArgument : TestBase, ITestArgument
{
    public TestArgument() { }
}

public class Example
{
    // The following method displays information about a generic
    // type.
    private static void DisplayGenericType(Type t)
    {
        Console.WriteLine($"\r\n {t}");
        Console.WriteLine($"   Is this a generic type? {t.IsGenericType}");
        Console.WriteLine($"   Is this a generic type definition? {t.IsGenericTypeDefinition}");

        // Get the generic type parameters or type arguments.
        Type[] typeParameters = t.GetGenericArguments();

        Console.WriteLine($"   List {typeParameters.Length} type arguments:");
        foreach (Type tParam in typeParameters)
        {
            if (tParam.IsGenericParameter)
            {
                DisplayGenericParameter(tParam);
            }
            else
            {
                Console.WriteLine($"      Type argument: {tParam}");
            }
        }
    }

    // Displays information about a generic type parameter.
    private static void DisplayGenericParameter(Type tp)
    {
        Console.WriteLine($"      Type parameter: {tp.Name} position {tp.GenericParameterPosition}");

        foreach (Type iConstraint in tp.GetGenericParameterConstraints())
        {
            if (iConstraint.IsInterface)
            {
                Console.WriteLine($"         Interface constraint: {iConstraint}");
            }
        }

        Console.WriteLine($"         Base type constraint: {tp.BaseType ?? tp.BaseType: None}");

        GenericParameterAttributes sConstraints =
            tp.GenericParameterAttributes &
            GenericParameterAttributes.SpecialConstraintMask;

        if (sConstraints == GenericParameterAttributes.None)
        {
            Console.WriteLine("         No special constraints.");
        }
        else
        {
            if (GenericParameterAttributes.None != (sConstraints &
                GenericParameterAttributes.DefaultConstructorConstraint))
            {
                Console.WriteLine("         Must have a parameterless constructor.");
            }
            if (GenericParameterAttributes.None != (sConstraints &
                GenericParameterAttributes.ReferenceTypeConstraint))
            {
                Console.WriteLine("         Must be a reference type.");
            }
            if (GenericParameterAttributes.None != (sConstraints &
                GenericParameterAttributes.NotNullableValueTypeConstraint))
            {
                Console.WriteLine("         Must be a non-nullable value type.");
            }
        }
    }

    public static void Main()
    {
        // Two ways to get a Type object that represents the generic
        // type definition of the Dictionary class.

        // Use the typeof operator to create the generic type
        // definition directly. To specify the generic type definition,
        // omit the type arguments but retain the comma that separates
        // them.
        Type d1 = typeof(Dictionary<,>);

        // You can also obtain the generic type definition from a
        // constructed class. In this case, the constructed class
        // is a dictionary of Example objects, with String keys.
        Dictionary<string, Example> d2 = [];
        // Get a Type object that represents the constructed type,
        // and from that get the generic type definition. The
        // variables d1 and d4 contain the same type.
        Type d3 = d2.GetType();
        Type d4 = d3.GetGenericTypeDefinition();

        // Display information for the generic type definition, and
        // for the constructed type Dictionary<String, Example>.
        DisplayGenericType(d1);
        DisplayGenericType(d2.GetType());

        // Construct an array of type arguments to substitute for
        // the type parameters of the generic Dictionary class.
        // The array must contain the correct number of types, in
        // the same order that they appear in the type parameter
        // list of Dictionary. The key (first type parameter)
        // is of type string, and the type to be contained in the
        // dictionary is Example.
        Type[] typeArgs = [typeof(string), typeof(Example)];

        // Construct the type Dictionary<String, Example>.
        Type constructed = d1.MakeGenericType(typeArgs);

        DisplayGenericType(constructed);
        _ = Activator.CreateInstance(constructed);

        Console.WriteLine("\r\nCompare types obtained by different methods:");
        Console.WriteLine($"   Are the constructed types equal? {d2.GetType() == constructed}");
        Console.WriteLine($"   Are the generic definitions equal? {d1 == constructed.GetGenericTypeDefinition()}");

        // Demonstrate the DisplayGenericType and
        // DisplayGenericParameter methods with the Test class
        // defined above. This shows base, interface, and special
        // constraints.
        DisplayGenericType(typeof(Test<>));
    }
}

См. также


Дополнительные ресурсы