如何将C#泛型方法调用分派到专门的方法调用

6

我有以下的C#类:

public class MyType<T>
{
   public void TryParse(string p_value)
   {
      T value ;

      Parser.TryParse(p_value, out value);

      // Do something with value
   }
}

关键是根据泛型类型T调用正确的Parser.TryParse方法。

这使用以下静态类:

static public class Parser
{
   static public void TryParse(string p_intput, out object p_output)
   {
      // Do something and return the right value
   }

   static public void TryParse(string p_intput, out double p_output)
   {
      // Do something and return the right value
   }

   static public void TryParse(string p_intput, out int p_output)
   {
      // Do something and return the right value
   }
}

我原本以为这会起作用:在最坏的情况下,“对象”TryParse将被调用。但是,我遇到了两个编译错误:
  • CS1502:'Parser.TryParse(string, out object)' 的最佳重载方法匹配具有一些无效的参数
  • CS1503:第2个参数:“out T”无法转换为“out object”
问题1:我不明白为什么这行不通:我可能很天真,但不是所有C#对象都应该派生自“object”吗?为什么T不能转换为object? 问题2:如何将带有泛型类型T的方法分派到正确的非泛型方法中(即MyType<T>.TryParse根据T的类型调用正确的Parser.TryParse)?

注意

问题已编辑以反映原始问题意图(如标题中所写:如何将C#泛型方法调用分派到专门的方法调用中


据我所知,只有在.NET 4.0中使用dynamic关键字才能实现多方法分派,具体描述请参见这里 - Roly
4个回答

6
实际上,refout参数不允许类型变化。因此,要将一个变量传递给期望一个out object参数的方法,该变量必须声明为object
从规范(§10.6.1.2和§10.6.1.3)中可以看到:
当形式参数是引用参数时,在方法调用中对应的参数必须由关键字ref和与形式参数相同类型的变量引用(§5.3.3)组成。
当形式参数是输出参数时,在方法调用中对应的参数必须由关键字out和与形式参数相同类型的变量引用(§5.3.3)组成。
参见:为什么refout参数不允许类型变化?以了解更多信息。
奖励问题:如何将具有泛型类型T的方法分派到正确的非泛型方法(即,根据T的正确类型调用MyType<T>.TryParse调用正确的Parser.TryParse)?
我要把问题反过来问你。为什么要这样做?如果你调用MyType<int>.TryParse,为什么不直接调用Int32.TryParse?这个额外的层次给你带来了什么好处?

我有一个通用类,其中包含许多通用代码。如果使用C++的模板,它本来可以工作的。在C#中,我要么必须将通用代码移动到子类中(每个类型都需要一个子类,例如使用Int32.TryParse作为Int32),但是拷贝/粘贴代码对我来说似乎有些不合适。因此,我试图在这里使用通用代码。 - paercebal
C# 的泛型和 C++ 的模板本质上是非常不同的东西。不要试图在 C# 中编写 C++ 代码。 - jason
我知道C++模板和C#泛型是不同的。我的问题是,一旦你使用了泛型,如果不依赖于接口(没有TryParse的接口)或反射,如何回退到使用非泛型代码? - paercebal

2

我知道这有点低科技,但我曾经遇到过同样的问题,解决方法是创建一个包含各个解析器的Dictionary<Type, Parser>。我对这个问题的答案很感兴趣。

祝好,Morten


在支持反射和泛型的语言中,不得不使用类型字典对我来说似乎有些不妥。但显然,这似乎是唯一的解决方案(除非使用反射,这同样不妥...) - paercebal
+1 因为这个解决方案具有实际价值(尽管我不喜欢它...但是,这是一个语言问题,你提供了一个解决方案!) - paercebal

2

当前解决方案

我在工作中使用的当前解决方案基于动态分派,即C# 4.0中定义的 dynamic 关键字。

代码大致如下(根据记忆):

public class Parser
{
   static public void TryParse<T>(string p_input, out T p_output)
   {
      // Because m_p is dynamic, the function to be called will
      // be resolved at runtime, after T is known...
      m_p.DoTryParse(p_input, out p_output) ;
   }

   // The dynamic keyword means every function called through
   // m_p will be resolved at runtime, at the moment of the call
   private dynamic m_p = new Parser() ;

   // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

   private void DoTryParse(string p_input, out double p_output)
   { /* Do something and return the right value */ }

   private void DoTryParse(string p_input, out int p_output)
   { /* Do something and return the right value */ }

   // etc.

   private void DoTryParse<T>(string p_input, out T p_output)
   {
      // fallback method... There are no dedicated method for T,
      // so p_output becomes the default value for T
      p_output = default(T) ;
   }
}

优雅的部分在于它不会失败(如果找不到更好签名匹配的函数,将调用回退函数),并且遵循简单的模式(重载函数)。
当然,实际的生产代码有所不同,并且更加复杂,因为我只有一个公共静态方法,我想要:
1.解析引用对象(类)和值对象(结构体); 2.解析枚举; 3.解析可为空类型; 4.我想要为用户提供从Parser派生的可能性,以提供除默认重载之外的其它重载。
但是我猜测,在当前解决方案中使用动态语言与原始答案中进行反射做的事情最终是一样的。只是“符号”的变化而已。
因此,我的方法如下:
public class Parser
{
   static public void TryParse<T>(string p_input, out T p_output)
   {
      // etc.
   }
}

可以解析任何东西,包括在代码是通用的情况下(因为编译时不知道T)。

原始答案

Jason的答案关于第一个问题(有关编译器错误)是正确的。但是,我没有解决我的问题(根据运行时的泛型类型T从通用方法分派到非通用方法)。

我尝试了LukeH的答案,但它没有起作用:无论什么情况下都会调用通用方法(即使删除第二个参数的out限定符也是如此)。

Morten的答案是最明智的答案,但它没有利用反射。

所以,为了解决自己的问题,我使用了反射。这需要重写通用的TryParse方法:

public class MyType<T>
{
   public void TryParse(string p_value)
   {
      T value = default(T);

      // search for the method using reflection
      System.Reflection.MethodInfo methodInfo = typeof(Parser).GetMethod
         (
            "TryParse",
            new System.Type[] { typeof(string), typeof(T).MakeByRefType() }
         );

      if (methodInfo != null)
      {
         // the method does exist, so we can now call it
         var parameters = new object[] { p_value, value };
         methodInfo.Invoke(null, parameters);
         value = (T)parameters[1];
      }
      else
      {
         // The method does not exist. Handle that case
      }
   }
}

如果需要,我可以提供源代码。

1

这个问题让我很感兴趣,所以我进行了一些研究,并找到了Paul Madox的一个好东西。这似乎可以解决问题。

   public static T SafeParseAndAssign<T>(string val) where T: new()
    {
        try
        {
            T ValOut = new T();

            MethodInfo MI = ValOut.GetType().
              GetMethod("Parse", new Type[] { val.GetType() });

            return (T)MI.Invoke(ValOut, new object[] { val });
        }
        catch
        {
            // swallow exception
        }
        return default(T);
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接