Как отправить вызов универсального метода C # в вызовы специализированных методов - PullRequest
6 голосов
/ 07 марта 2011

У меня есть следующий класс C #:

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

      Parser.TryParse(p_value, out value);

      // Do something with value
   }
}

Смысл в том, чтобы вызвать правильный метод Parser.TryParse, в зависимости от универсального типа T.

При этом используется следующий статический класс:

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 # не должны быть производными от «объекта»? Почему T нельзя преобразовать в объект?

Вопрос 2: Как я могу отправить метод с универсальным типом T в правильные неуниверсальные методы (то есть MyType<T>.TryParse, вызывающий правильное Parser.TryParse в соответствии с правом тип Т)?

Примечание

Вопрос был отредактирован, чтобы отразить исходное намерение вопроса (как написано в заголовке: Как отправить вызов универсального метода C # в вызовы специализированных методов )

Ответы [ 4 ]

6 голосов
/ 07 марта 2011

На самом деле, параметры ref и out не допускают изменения типа.Таким образом, чтобы передать переменную методу, ожидающему параметр out object, эта переменная должна быть объявлена ​​как object.

Из спецификации (§10.6.1.2 и §10.6.1.3)

Когда формальный параметр является ссылочным параметром, соответствующий аргумент в вызове метода должен состоять из ключевого слова ref, за которым следует ссылка на переменную (§5.3.3) того же типа, что и формальный параметр.

Когда формальный параметр является выходным параметром, соответствующий аргумент в вызове метода должен состоять из ключевого слова out, за которым следует ссылка на переменную (§5.3.3) того же типа, что и формальный параметр,

См .: Почему параметры ref и out не допускают изменения типа? , чтобы понять почему.

Дополнительный вопрос:Как я могу отправить метод с универсальным типом T в правильные неуниверсальные методы (например, MyType<T>.TryParse, вызывающий правильное Parser.TryParse в соответствии с правильным типом T)?

Я собираюсь повернуть это обратно на вас.Почему вы это делаете?Если вы вызываете MyType<T>.TryParse как, скажем, MyType<int>.TryParse, почему бы не позвонить Int32.TryParse напрямую?Что этот дополнительный слой покупает у вас?

2 голосов
/ 08 марта 2011

Текущее решение

Текущее решение, которое я использую на работе, основано на динамической диспетчеризации, то есть на динамическом ключевом слове, как определено в C # 4.0.

Код похож на (из памяти):

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) ;
   }
}

Элегантная часть состоит в том, что она не может завершиться ошибкой (функция отката будет вызвана, если не найдена ни одна из них с лучшим совпадением сигнатур), и что она следует простому шаблону (перегрузитеfunction).

Конечно, реальный, производственный код несколько отличается и более сложен, потому что, имея только один публичный статический метод, я хочу:

  1. разобрать обассылочные объекты (классы) и объекты-значения (структуры)
  2. перечисления enum
  3. разбираемые типы, допускающие синтаксический анализ
  4. Я хочу предложить пользователю возможность получить из Parser возможность предложить свой собственныйперегрузки в дополнение к настройкам по умолчанию

Но я предполагаю, что использование динамического в текущем решении, в конце концов, то же самое, что и отражение, как в первоначальном ответениже.Изменяется только «нотация».

Заключение, у меня теперь есть следующий метод:

public class Parser
{
   static public void TryParse<T>(string p_input, out T p_output)
   {
      // etc.
   }
}

, который может анализировать что угодно, в том числе в ситуациях, когда T не известно во время компиляции (потому что код является общим).

Оригинальный ответ

Ответ Джейсона был прав насчет первого вопроса (об ошибках компилятора).Тем не менее, у меня не было решения моей проблемы (отправка из универсального метода в неуниверсальные методы в соответствии с универсальным типом времени выполнения T).

Я пытался ответ LukeH , но это не такt работа: универсальный метод вызывается всегда, несмотря ни на что ( даже при удалении квалификатора out второго параметра ).

Ответ Мортена - этосамый разумный, который должен работать, но не использует отражения.

Итак, чтобы решить свою проблему, я использовал отражение.Для этого необходимо переписать универсальный метод 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
      }
   }
}

У меня есть исходный код, если необходимо.

2 голосов
/ 07 марта 2011

Я знаю, что это несколько низкотехнологично, но у меня была та же проблема, когда я решил ее, создав Dictionary<Type, Parser>, содержащий отдельные парсеры. Мне будет интересно, какие ответы принесут эти вопросы.

С уважением, Мортен

1 голос
/ 08 марта 2011

Эта проблема заинтриговала меня, поэтому я провел небольшое исследование и нашел замечательную вещь Пол Мэдокс . Это, кажется, делает трюк.

   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);
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...