【问题标题】:Extension methods with base and sub-classes具有基类和子类的扩展方法
【发布时间】:2015-06-04 13:42:33
【问题描述】:

更新

请求重新打开,因为其他 SO 答案没有解决方案,但问题的其中一个 cmet 有一个我想接受的解决方案,因为它适用于该场景。

原始问题

我在使用非抽象基类和选择适当扩展方法的子类编写扩展方法时遇到问题。

Example Code

我在下面有一个非常简单的示例(从一个更大的项目中提取),它使用了扩展方法“Run”。预期的输出列在每个类旁边的注释中。

public class Parent { }; // Should output "Parent"
public class ChildA : Parent { }; // Should output "Child A"
public class ChildB : Parent { }; // Should output "Parent"

// Expected Output: ChildA, Parent, Parent
public class Program
{
    public static void Main()
    {
        var commands = new List<Parent>() { new ChildA(), new ChildB(), new Parent() };
        Console.WriteLine(string.Join(", ", commands.Select(c => c.Run())));
    }
}

到目前为止,这是我的尝试,但必须有一种更清洁的方法来做到这一点:

  1. 无类型检查 - 导致 Parent 扩展方法被独占使用(Parent、Parent、Parent)
  2. 显式类型检查 - 正确的输出,但必须显式检查每个扩展可能性的类型(ChildA、Parent、Parent)
  3. 尝试将 Convert.ChangeType 转换为动态类型 - 运行时异常,因为扩展无法捕获动态类型(无输出)
  4. 尝试使用反射进行泛型强制转换 - 尚未完全可操作,但不确定方法是否有效

尝试列表

public static class Extensions
{
    public static string Run(this ChildA model)
    {
        return "ChildA";
    }
    public static string Run(this Parent model)
    {
        return model.Run1(); // Change to test different approaches
    }
    public static string Run1(this Parent model) // No type-checking
    {
        return "Parent";
    }
    public static string Run2(this Parent model) // Explicitly check sub-types
    {
        if (model is ChildA)
            return ((ChildA)model).Run();
        else
            return "Parent";
    }
    public static string Run3(this Parent model) // Attempted dynamic type conversion
    {
        if (model.GetType().BaseType == typeof(Parent))
        {
            dynamic changedObj = Convert.ChangeType(model, model.GetType());
            return changedObj.Run();
        }
        else
            return "Parent";
    }
    public static string Run4(this Parent model) // Attempted reflected generic type conversion
    {
        if (model.GetType().BaseType == typeof(Parent))
        {
            var method = typeof(Extensions).GetMethod("Cast");
            var generic = method.MakeGenericMethod(new[] { model.GetType() });
            //var generic = generic.Invoke(new object(), null);
            //return generic.Run();
            return "Not working yet";
        }
        else
            return "Parent";
    }
    public static T Cast<T>(this object input)
    {
        return (T) input;   
    }

}

【问题讨论】:

  • 听起来你要找的是多态性,isn't available for extension methods
  • 扩展方法上的多态性正是这...感谢您找到基本相同的旧 SO 问题。我喜欢那里提到的访问者模式,因为应用程序正在做的事情,任何将扩展更改为实体模式的操作都是一个不错的选择。感谢 cmets!
  • 一种带有反射方法的方法可以工作:dotnetfiddle.net/iqXu8Y
  • @ASh - 这种方法效果很好。我指定这个问题重新打开,以便您可以根据需要将其作为答案。
  • 看起来更简单的方法是Console.WriteLine(string.Join(", ", commands.Select(c =&gt; Extensions.Run(c as dynamic)))); 取自@Damien_The_Unbeliever 的链接

标签: c# inheritance reflection extension-methods


【解决方案1】:

ParentChildA 创建两个扩展方法,您可以使用dynamic 将关联移动到运行时。

Console.WriteLine(string.Join(", ", commands.Select(c => Extensions.Run(c as dynamic)))); 

【讨论】:

  • 我添加了一个带有dynamic 陷阱的答案。请阅读
【解决方案2】:

最好的 Run 方法重载在编译时解决,对于 List&lt;Parent&gt; 的项目,它是 Run(this Parent model)。多态行为可以通过扩展方法中的反射来模仿

demonstration

using System;
using System.Reflection;
using System.Linq;
using System.Collections.Generic;

public static class Extensions
{
    private static Dictionary<Type, MethodInfo> _runs;
    private static Type  _parentType;

    static Extensions()
    {
        _parentType = typeof(Parent);        
        _runs = new Dictionary<Type, MethodInfo>();

        // overloads of Run method, which return string for different types derived from Parent
        var methods = typeof(Extensions)
                      .GetMethods(BindingFlags.Static|BindingFlags.Public)
                      .Where(m => m.Name == "Run" && m.ReturnType == typeof(string));

        foreach(var mi in methods)
        {
            var args = mi.GetParameters();
            //  method should have only one parameter
            if (args.Length != 1 || _parentType.IsAssignableFrom(args[0].ParameterType) == false)
                return;         
            _runs.Add(args[0].ParameterType, mi);
        }

    }

// 重载

    public static string Run(this ChildA model)
    {
        return "ChildA";
    }

    public static string Run(this Parent model, object args)
    {
        // this method is not added to _runs (2 parameters)
        return null;
    }

    public static int Run(this ChildC model)
    {
        // this method is not added to _runs (return int)
        return 0;
    }

    public static string Run(this Parent model) // Attempted dynamic type conversion
    {               
        // not really correct
        if (model == null)
            return "Parent";            
        var t = model.GetType();
        if (t == _parentType)       
            return "Parent";
        // invoke overload for type t
        if (_runs.ContainsKey(t))       
            return (string) _runs[t].Invoke(null, new object[] {model});        
        return "Not working yet";
    }       
}

// 用法

public class Parent { };          // Should output "Parent"
public class ChildA : Parent { }; // Should output "Child A"
public class ChildB : Parent { }; // Should output "Not working yet"
public class ChildC : Parent { };

public class Program
{
    public static void Main()
    {
        var commands = new List<Parent>() { new ChildA(), new ChildB(), new Parent(),  new ChildC(), (ChildA)null};
        Console.WriteLine(string.Join(", ", commands.Select(c => c.Run())));

        // extension method can be invoked for null
        Console.WriteLine(((ChildA)null).Run());

        //// crashes on (ChildA)null with error: 
        //// The call is ambiguous between the following methods or properties: 'Extensions.Run(ChildA)' and 'Extensions.Run(ChildC)'
        //Console.WriteLine(string.Join(", ", commands.Select(c => Extensions.Run(c as dynamic))));
    }
}

简历

扩展方法可以作为普通方法调用(.Run()),而不是静态Extensions.Run

扩展方法Run(this Parent model)null 参数有问题(无法正确解析类型)

dynamic 的技巧在大多数情况下都有效,但是:

  1. 调用int Run(this ChildC model) 方法,该方法返回int 而不是string 像其他人一样(当(ChildA)null 从列表中删除时)
  2. The call is ambiguous between the following methods or properties: 'Extensions.Run(ChildA)' and 'Extensions.Run(ChildC)' 错误而崩溃(我不明白)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-22
    • 2011-12-06
    • 1970-01-01
    • 1970-01-01
    • 2018-07-08
    相关资源
    最近更新 更多