将较少指定的接口IInterface转换为更明确的IInterface <tkey,tvalue>?

时间:2018-06-13 21:34:26

标签: c# generics reflection interface casting

我有两个接口

public interface ISerializableDictionary { ... }
public interface ISerializableDictionary<TKey,TValue>
    : ISerializableDictionary { ... }

我需要在运行时使用反射从前者转换为后者。

GetType().GetGenericArguments来查询前者显然很容易。

但是我怎么做演员呢?我有下面这段代码,但它无法编译,因为显而易见的原因是我试图将变量用作类型。

Type[] genericTypes = dictionary.GetType().GenericTypeArguments;
Type keyType = genericTypes[0];
Type valueType = genericTypes[1];

// this compiles but doesn't do the cast
Type dictType = typeof(SerializableDictionary<,>).MakeGenericType(keyType, valueType);
var createdDictionary = Activator.CreateInstance(dictType);

// this is the line that won't compile - 'dictionary' is a SerializableDictionary, and I want to access it through its typed generic interface
ISerializableDictionary<keyType,valueType> serializableDictionary = dictionary as ISerializableDictionary<keyType, valueType>;

指定的接口有一个我需要调用的方法。指定较少的接口不会(也不可能,因为调用需要一个类型参数)。

解决方案是否与dictionary.GetType().GetInterfaces()有关?

任何操纵者都会受到高度赞赏。目前编程独奏所以我没有一个团队可以打电话,因此这里的查询。

更新 - 回应评论

我想解决的问题是如何序列化成员本身可枚举的对象的成员。我试图弄清楚序列化库如何将其作为一种学习练习,因为我有一些想要探索的想法。序列化&amp;反思不是我编程的主要方面,所以我很难学会它们。

所以我(减少代码):

public class ExperimentalSerializer<T>
{

    public void Serialize(T objectToSerialize)
    {

        IEnumerable<object> collectionsToSerializeToCSV = objectToSerialize.GetEnumerableMembers();
        foreach (object collectionToSerialize in collectionsToSerializeToCSV)
        {

            string csvString      = "";
            if (collectionToSerialize.IsDictionary())
            {
                // serialize dictionary here to csvString
                // but cannot properly access contents through just IDictionary
                // need IDictionary<TKey,TValue>
                // ** ALSO SEE TEXT BELOW THIS CODE SNIPPET**
            }
            else if (collectionToSerialize.IsList())
            {
                // serialize list here to csvString
            }
            else if (collectionToSerialize.GetType().IsArray)
            {
                // serialize array here to csvString
            }

            // save csvString to somewhere useful here

        }

    }

}

在其他地方我有一个扩展方法:

public static IEnumerable<object> GetEnumerableMembers(this object objectToInterrogate)
{
    Type                      objectType               = objectToInterrogate.GetType();

    // get the enumerable properties
    PropertyInfo[]            properties               = objectType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
    IEnumerable<PropertyInfo> enumerableProperties     = properties.Where(propertInfo => propertInfo.PropertyType.GetInterfaces().Any(x => x == typeof(IEnumerable)));
    IEnumerable<PropertyInfo> serializableProperties   = enumerableProperties.Where(p => p.IsSerializable());
    IEnumerable<object>       enumerablePropertyValues = serializableProperties.Select(p => p.GetValue(objectToInterrogate, null));

    // get the enumerable fields
    FieldInfo[]               fields                   = objectType.GetFields(BindingFlags.Instance | BindingFlags.Public);
    IEnumerable<FieldInfo>    enumerablefields         = fields.Where(propertInfo => propertInfo.FieldType.GetInterfaces().Any(x => x == typeof(IEnumerable)));
    IEnumerable<object>       enumerablefieldValues    = enumerablefields.Select(f => f.GetValue(objectToInterrogate));

    // merge the two lists together
    IEnumerable<object>       enumerableMembers        = enumerablePropertyValues.Union(enumerablefieldValues);
    return enumerableMembers.ToList();
}

我正在研究的一个具体挑战是如何序列化一个可枚举的DictionaryList或数组TValue[]),其中TValue本身就是一个复杂类型(例如一个类)可以序列化)。如果不知道TValue的类型,就无法确定这一点,但无法单独从IDictionaryIList检索这些内容,而这些只能使用object类型进行枚举。

这是我正在尝试调查并可能控制的非常具体的一点:如何确定TValue,然后确定是否/如何依次序列化它。我的想法是使用已知类型参数转换为更具指定性的泛型,但此时我有点迷失。

希望这有帮助。

1 个答案:

答案 0 :(得分:1)

@SLaks在评论中指出:

  

Casting本质上是一个编译时操作。转换为仅在运行时已知的类型是没有意义的。如果在编译时不知道它的类型,则无法调用方法。

这是绝对正确的。当然,您仍然可以在运行时调用预期的方法,但是您需要使用(更多)反射来执行此操作,因为您无法让编译器生成静态类型的调用。

要执行此操作,请使用Type获取您已构建的MakeGenericType()对象,并在其上调用GetMethod()以获取与要调用的方法相对应的Type.MethodInfo对象。然后,拨打MethodInfo.Invoke()

Type dictType = typeof(SerializableDictionary<,>).MakeGenericType(keyType, valueType);

MethodInfo method = dictType.GetMethod("MyMethod");
object returnValue = method.Invoke(dictionary, new object[] { /* arguments */ });

TMI ...

当您编写dictionary.MyMethod()时,C#编译器会生成Callvirt IL(字节代码)指令。调用方法的对象(以及方法的参数)被压入堆栈,Callvirt的参数是与类型限定的ISerializableDictionary<TKey,TValue>.MyMethod方法对应的元数据标记。这是.NET中的常规调用机制。当您(和编译器)在编译时不知道TKeyTValue是什么时,就无法为该方法获取正确的元数据标记,也无法生成{{1 }}。这就是你必须使用反射API的原因。

但是,您可以使用类似DynamicMethod的内容来生成您自己的IL 并在运行时对其进行JIT。一旦JITted,调用与编译器静态生成的调用一样快。生成动态方法当然有很大的开销,但这是一次性开销。

当然,@ David L指出:

  

这里的方法似乎非常偏僻。您可以描述一下您要解决的具体问题,而不是要求具体的解决方案吗?

那也是绝对正确的。所以不要做我刚刚建议的,除非你真的,真的知道你在做什么,并且有一个非常非常好的理由。 (提示:你没有。)但我认为这些信息可能会让你更好地了解为什么你不能做你想做的事情。