【问题标题】:Pass generic method omitting its parameters?传递泛型方法省略其参数?
【发布时间】:2015-11-27 02:56:53
【问题描述】:

有没有办法以通用方式传递方法名称,而不传递其参数,因此可以通过传递参数的方法调用它?

考虑这个例子:

public class Client
{
  public string Convert(int value)
  {
    return value.ToString();
  }
}

public class Wrapper<TClient>
{
  TClient Client;

  public TResult Invoke<TArg, TResult>(Func<TClient, TArg, TResult> action, TArg arg)
  {
    return action(Client, arg);
  }
}

我希望能够将我想要调用的TClient 方法传递给包装器,并传递实际参数,所有这些都是一般性的:

var wrapper = new Wrapper<Client>();
wrapper.Invoke(c => c.Convert, 5);

是否有任何可能的方法来实现这一点,而无需对方法名称进行硬编码或失去其通用性(即通过使用Delegate)?

注意事项:

Client 是一个外部密封类,它公开了许多参数中的每一个的无数方法。我想包装它的行为,我不介意在包装器中编写所有必要的代码,但是包装器的使用应该尽可能干净。

更新
我想避免需要指定参数。整个想法是让它们从指定的动作中推断出来。

【问题讨论】:

    标签: c# generics lambda expression-trees


    【解决方案1】:

    您非常接近让您的代码运行。有两种选择。

    首先,你可以试试这个:

    public class Wrapper<TClient>
    {
        public TResult Invoke<TArg, TResult>(Func<TArg, TResult> action, TArg arg)
        {
            return action(arg);
        }
    }
    

    然后这样称呼它:

    var wrapper = new Wrapper<Client>();    
    wrapper.Invoke(wrapper.client.Convert, 5);
    

    或者,您也可以这样做:

    public class Wrapper<TClient>
    {
        public Wrapper(TClient client)
        {
            this.Client = client;
        }
    
        private TClient Client;
    
        public TResult Invoke<TArg, TResult>(Func<TClient, TArg, TResult> action, TArg arg)
        {
            if (operation.Target != Client)
              throw new ArgumentException(nameof(operation));
    
            return action(this.Client, arg);
        }
    }
    

    然后这样称呼它:

    var client = new Client();
    var wrapper = new Wrapper<Client>(client);
    
    wrapper.Invoke((c, a) => c.Convert(a), 5);
    

    但是,根据您对问题的描述,我看不出这两种方法有什么帮助,也看不出如何实现您的要求。或许您需要更详细地说明您要解决的潜在需求是什么?

    【讨论】:

    • 第一个 sn-p 的问题是您正在实例化 Client,而不是使用存储在 Wrapper 中的那个。
    • 我希望两者混合使用,所以我不需要 1) 必须为用户提供对客户端的引用,2) 所以我不必命名所有参数(会有很多!)。我想我会选择你的第一个解决方案,因为命名参数比暴露客户端更让我害怕。
    • 我已经更新了你的代码,按照我打算这样做的方式,无论如何都会包含客户端。
    【解决方案2】:

    您希望传递给Invoke 的表达式返回一个接受TArgFunc。在代码中:

    public class Wrapper<TClient>
    {
        TClient Client;
    
        public TResult Invoke<TArg, TResult>(Func<TClient, Func<TArg, TResult>> action, TArg arg)
        {
            return action(Client)(arg);
        }
    }
    

    然后你可以像这样调用它:

    class Program
    {
        static void Main(string[] args)
        {
            var wrapper = new Wrapper<Client>();
            string result = wrapper.Invoke<int, string>(c => c.Convert, 5);
        }
    }
    

    由于您不喜欢必须显式指定类型参数的方法,您可以使用稍微不同的 API(它有自己的烦恼):

    public class Wrapper<TClient>
    {
        TClient Client;
    
        public void Invoke<TArg, TResult>(Func<TClient, Func<TArg, TResult>> action, TArg arg, out TResult result)
        {
            return action(Client)(arg);
        }
    }
    

    您可以这样调用它,返回类型从 out 参数推断:

    class Program
    {
        static void Main(string[] args)
        {
            var wrapper = new Wrapper<Client>();
            string result;
            wrapper.Invoke(c => c.Convert, 5, out result);
        }
    }
    

    【讨论】:

    • 我想避免指定类型。客户端公开了无数种不同数量的参数中的每一种方法。我的目标是避免再次指定所有参数类型,并使包装器的使用尽可能顺畅。
    • @Shimmy 不幸的是,如果不明确指定类型,就不可能将方法组隐式解析为Func,因为编译器对方法重载很谨慎。
    • 任何解决方法?问题是一旦你必须指定一个类型参数,你就必须指定所有参数,对吗?
    • 我不介意省略 TResult 是否让情况变得更好?
    • @Shimmy 这行得通,如果你不介意像这样传递一个包装 lambda:c =&gt; arg =&gt; c.Convert(arg)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多