如何显式转换 'object' 类型的变量以满足多类型泛型约束?

How can you explicitly cast a variable of type 'object' to satisfy a multi-typed generic constraint?

我们有一个通用扩展方法,它仅适用于同时支持 INotifyCollectionChanged 和 IEnumerable 接口的对象。是这样写的:

public static class SomeExtensions
{
    public static void DoSomething<T>(this T item)
    where T : INotifyCollectionChanged, IEnumerable
    {
        // Do something
    }
}

下面的编译很好,因为 ObservableCollection<string> 实现了两个接口,并且由于 'var' 关键字,knownType 是强类型的:

var knownType = new ObservableCollection<string>();
knownType.DoSomething();

但是,我们正在尝试从 ValueConverter 调用它。问题是传入的值是 object 类型,即使我们可以测试传入的对象确实实现了两个接口,我们也无法弄清楚如何显式转换它,因此我们可以调用通用扩展方法.这行不通(显然)...

object unknownType = new ObservableCollection<string>();
((INotifyCollectionChanged,IEnumerable)unknownType).DoSomething();

那么如何将一个对象转换为多个接口以便调用泛型?甚至不确定这是否可能。

我能想到的最简洁的方法是 dynamic。但是,由于扩展方法不能很好地与 dynamic 配合使用(见下文),您必须显式引用扩展方法。

Extensions.DoSomething(unknownType as dynamic);

EDIT 作为对我 运行 的陷阱的警告,请注意使用显式 dyanmic 作为类型参数调用方法(通过 DoSomething<dynamic>) 将 not 工作 - 它会在尝试匹配多个约束时导致编译错误。此外,当不使用多个约束时,这会导致 dynamic 根据传递的变量的编译时类型进行解析, 而不是 运行时类型。

这将导致调用 Extensions.UnknownType<dynamic>,其中的 dynamic 将在运行时解析 - 这意味着它将使用给定参数的完全派生类型。只要这个参数实现了所需的接口,就可以了。

请注意,与许多 dynamic 代码一样,这可能会遇到直到运行时才会出现的问题。谨慎使用!

如果您使用相同的通用参数进行多次调用,您最好在转换器中添加一个通用辅助方法,然后使用 value as dynamic[调用 that

附录:

使用 dynamic 时,针对 dynamic 对象调用的任何内容都将尝试在运行时解析为给定类型的成员,但 不会 查找扩展方法,因为它们本质上存在于完全不同的 class 中,通常是不同的程序集。

您可以尝试使用 MethodInfo 和委托来检索对正确方法的委托。

以下解决方案使用动态,但使用了一点反射。 该方法基本上执行以下操作:

  1. 检索您要调用的方法的委托,但为另一个已知类型。
  2. 从中检索泛型方法定义。
  3. 然后检索对象类型的 MethodInfo
  4. 通过调用最终的 MethodInfo.
  5. 来调用扩展方法

_Placeholder class 是用于获取初始委托的已知类型,但您可以使用满足通用约束的任何其他类型。

这样您还可以在编译时检查扩展方法的名称及其限制。

一个好的方法还可以缓存最终的 MethodInfo(或委托),以避免重复昂贵的调用。


private sealed class _Placeholder : INotifyCollectionChanged, IEnumerable
{
    public event NotifyCollectionChangedEventHandler CollectionChanged;

    public IEnumerator GetEnumerator() { throw new NotImplementedException(); }
}

static void Test(object obj)
{
    if ((obj is INotifyCollectionChanged) && (obj is IEnumerable))
    {
        var typeObject = obj.GetType();

        // Retrieve the delegate for another type
        Action<_Placeholder> _DoSomething = SomeExtensions.DoSomething<_Placeholder>;
        // Retrieve the generic definition
        var infoTemp = _DoSomething.Method.GetGenericMethodDefinition();
        // Retrieve the MethodInfo for our object's type
        var method = infoTemp.MakeGenericMethod(typeObject);

        // Call the extension method by invoking
        method.Invoke(null, new[] { obj });

        //If you can return the delegate, you can try the following:
        //var typeDelegate = typeof(Action<>).MakeGenericType(typeObject);
        //var action = Delegate.CreateDelegate(typeDelegate, method);
        //((Action<_Test>)action)(obj as _Test);
    }
}