简体   繁体   English

调用 Activator.CreateInstance() 实例的方法的类型安全方法?

[英]Type-safe way to call the methods of the instances of Activator.CreateInstance()?

In the following code, hs.Add(...) cannot compile.在以下代码中, hs.Add(...)无法编译。 Declare hs as dynamic should work but I'm trying not to use it.hs声明为dynamic应该可以工作,但我试图不使用它。 Is there a type-safe way?有没有类型安全的方法?

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

I tried the following since the type of compile-time type of x is unknown.由于x的编译时类型未知,我尝试了以下操作。

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

but it got error of但它有错误

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

Like Peter pointed out, Activator is a method of working with reflection to dynamically create objects without knowing the type at build time.就像 Peter 指出的那样, Activator是一种使用反射来动态创建对象的方法,而无需在构建时知道类型。 Type "safety" in this method is though checking types or handle type conversion errors such as with Convert.ChangeType .此方法中的类型“安全”是通过检查类型或处理类型转换错误,例如使用Convert.ChangeType

Note that DbDataReader has some helper functions to work with types such as GetFieldType and GetString which can be called instead using the column index.请注意, DbDataReader 有一些辅助函数来处理诸如 GetFieldType 和 GetString 之类的类型,可以使用列索引来调用它们。 For example:例如:

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

It more or less boils down to some using branching statements instead of reflection to manage your types.它或多或少归结为一些使用分支语句而不是反射来管理您的类型。 Also, you should note that many ORMs like EntityFramework or Dapper, handle a lot of this type conversion for you.此外,您应该注意到许多 ORM,如 EntityFramework 或 Dapper,为您处理了很多这种类型转换。

It seems you're wanting to perform some kind of Set Operation (Intersection? Exclusion?) over scalar values.您似乎想要对标量值执行某种设置操作(交集?排除?)。 You can solve this problem using generics (and DbDataReader.GetFieldValue<T> ) without needing to use reflection or Activator , like so:您可以使用泛型(和DbDataReader.GetFieldValue<T> )解决此问题,而无需使用反射或Activator ,如下所示:

If you intend to call this F function from call-sites where the type of the colName column is known statically (ie at compile time), then convert F to a generic method:如果您打算从静态已知colName列的类型(即在编译时)的调用站点调用此F函数,则将F转换为通用方法:

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

Used like so:像这样使用:

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

If the Type being used can only be known at runtime (eg because of user-supplied SQL queries) then we'll have to use Activator , but can still use a HashSet<> indirectly by wrapping it in an adapter:如果使用的 Type 只能在运行时知道(例如因为用户提供的 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.
}

Here is the version using reflection.这是使用反射的版本。

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

It's slower than dynamic if .Invoke() needs to be called millions of times.如果.Invoke()需要被调用数百万次,它比dynamic慢。 dynamic seems need more time to setup. dynamic似乎需要更多时间来设置。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM