Типобезопасный способ вызова методов экземпляров Activator.CreateInstance ()? - PullRequest
0 голосов
/ 14 марта 2020

В следующем коде hs.Add(...) не может скомпилироваться. Объявить hs как dynamic должно работать, но я пытаюсь не использовать его. Существует ли безопасный для типов способ?

void F(string colName, DbDataReader reader1, DbDataReader reader2)
{
    // .... loop reader1.Read(); reader2.Read()
    var x = reader1[colName];
    var type = x.GetType();
    var hs = Activator.CreateInstance(typeof(HashSet<>).MakeGenericType(type)); 
      // change var to dynamic will work. Trying not to use it.
    hs.Add(x); // Error
    // ...
    hs.Contains(reader2[colName]); // Error

Я пробовал следующее, поскольку тип времени компиляции x неизвестен.

var hs = (HashSet<object>)Activator.CreateInstance(typeof(HashSet<>).MakeGenericType(type)); 

, но он получил ошибку

System.InvalidCastException: Unable to cast object of type
  'System.Collections.Generic.HashSet`1[System.Int32]' to type
  'System.Collections.Generic.HashSet`1[System.Object]'.

Ответы [ 3 ]

1 голос
/ 14 марта 2020

Кажется, вы хотите выполнить какую-то операцию над множеством (пересечение? Исключение?) Над скалярными значениями. Вы можете решить эту проблему с помощью обобщений (и DbDataReader.GetFieldValue<T>) без необходимости использования отражения или Activator, например, так:

Если вы намереваетесь вызывать эту функцию F с сайтов вызова, где тип столбец colName известен статически (т. е. во время компиляции), затем преобразуйте F в общий метод c:

void F<T>( String colName, DbDataReader reader1, DbDataReader reader2 )
{
    Itn32 rdr1Idx = reader1.GetOrdinal( colName );
    Itn32 rdr2Idx = reader2.GetOrdinal( colName );

    HashSet<T> hashSet = new HashSet<T>();

    while( reader1.Read() && reader2.Read() )
    {
        T value1 = reader1.GetFieldValue<T>( rdr1Idx );
        T value2 = reader2.GetFieldValue<T>( rdr2Idx );

        hashSet.Add( value1 );
        hashSet.Add( value2 );
        // etc - whatever logic you want here.
    }
}

Используется примерно так:

F<Int32>( "someIntegerColumn", rdr1, rdr2 );
F<float>( "someNumericColumn", rdr1, rdr2 );
// etc

Если используемый тип может быть известен только во время выполнения (например, из-за предоставленных пользователем SQL запросов), тогда мы должны будем использовать Activator, но все еще можем использовать HashSet<> косвенно, оборачивая его в адаптер:

using System.Collections;

void F( String colName, DbDataReader reader1, DbDataReader reader2 )
{
    Itn32 rdr1Idx = reader1.GetOrdinal( colName );
    Itn32 rdr2Idx = reader2.GetOrdinal( colName );

    Type columnType1 = reader1.GetFieldType( rdr1Idx );
    Type columnType2 = reader2.GetFieldType( rdr2Idx );
    if( columnType1 != columnType2 ) throw new InvalidOperationException( "Columns have different types." );

    var hashSet = (IList)Activator.CreateInstance( typeof(List<>).MakeGenericType( columnType1 ) );

    while( reader1.Read() && reader2.Read() )
    {
        Object value1 = reader1.GetValue( rdr1Idx );
        Object value2 = reader2.GetValue( rdr2Idx );

        hashSet.Add( value1 ); // HashSetAdapter will cast `Object value1` to its internal `T` type.
        hashSet.Add( value2 );
        // etc - whatever logic you want here.
    }
}

class HashSetAdapter<T> : IList<T> // TODO: Implement all of System.Collections.ICollection
{
    private readonly HashSet<T> hs;

    public HashSetAdapter()
    {
        this.hs = new HashSet<T>();
    }

    public void Add( Object value )
    {
        this.hs.Add( (T)value );
    }

    // TODO: Expose any other HashSet operations.
}
1 голос
/ 14 марта 2020

Как указал Питер, Activator - это метод работы с отражением для динамического создания объектов без знания типа во время сборки. Тип "safety" в этом методе проверяет типы или обрабатывает ошибки преобразования типов, например, с Convert.ChangeType.

. Обратите внимание, что DbDataReader имеет некоторые вспомогательные функции для работы с такими типами, как GetFieldType и GetString, которые можно вызывать вместо используя индекс столбца. Например:

int myColumn = 0;
var type = reader1.GetFieldType(myColumn);
if (type == typeof(string))
   string myValue = reader1.GetString(myColumn);

Это более или менее сводится к тому, что некоторые используют операторы ветвления вместо отражения для управления вашими типами. Кроме того, вы должны заметить, что многие ORM, такие как EntityFramework или Dapper, выполняют для вас многие преобразования этого типа.

0 голосов
/ 15 марта 2020

Вот версия с использованием отражения.

var x = 1;

var type = x.GetType();
var hs = Activator.CreateInstance(typeof(HashSet<>).MakeGenericType(type));
// hs.Add(x); // Error
var method = hs.GetType().GetMethod("Add");

method.Invoke(hs, new object[] { x });
method.Invoke(hs, new object[] { x + 1 });

Это медленнее, чем dynamic, если .Invoke() нужно вызывать миллионы раз. dynamic кажется, нужно больше времени для настройки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...