Почему Fun c с обнуляемым типом возврата не помещается в словарь, содержащий функции с типами возврата объекта? - PullRequest
2 голосов
/ 04 февраля 2020

Я пытаюсь построить Dictionary, который выглядит следующим образом:

var getValueFuncs = new Dictionary<TypeID, Func<string, object>>
{
  {TypeID.BINARY, NotMine.GetBinaryValue},
  {TypeID.BOOLEAN, NotMine.GetBooleanValue},
  {TypeID.DATE, NotMine.GetDateTimeValue},
  {TypeID.DOUBLE, NotMine.GetFloat64Value},
  {TypeID.LONG, NotMine.GetInteger32Value},
  {TypeID.STRING, NotMine.GetStringValue}
};

Различные Func s, которые я хочу вставить во все, имеют разные типы возврата.

public interface INotMine : ICollection, ICloneable
{
  byte[] GetBinaryValue(string propName);
  bool? GetBooleanValue(string propName);
  DateTime? GetDateTimeValue(string propName);
  double? GetFloat64Value(string propName);
  int? GetInteger32Value(string propName);
  string GetStringValue(string propName);
}

Обратите внимание, что NotMine - это отдельный класс, который наследуется от INotMine, и я не могу его изменить.

Две строки для инициализации Dictionary с процедурами, которые возвращают byte[] и string компилируются без проблем. Четыре строки для процедур, которые возвращают обнуляемые типы данных (то есть bool?, DateTime?, double? и int?) не скомпилируют . Каждый из них сообщает об ошибке, подобной этой:

bool? INotMine.GetBooleanValue(string propName) 
'bool? INotMine.GetBooleanValue(string)' has the wrong return type 
Expected a method with 'object GetBooleanValue(string)' signature

Учитывая, что все наследуется от object, почему эти строки не скомпилируются и go в мой Dictionary красиво?

РЕДАКТИРОВАТЬ: Прежде чем он удалил его, кто-то опубликовал ответ, который содержал элегантный обходной путь, который выглядел так:

    {TypeID.BINARY, s => NotMine.GetBinaryValue(s)}

Я благодарен за это, и я буду использовать это, но я все еще хотел бы знать, почему оригинальные строки не будут компилироваться.

1 Ответ

4 голосов
/ 05 февраля 2020

Примите во внимание следующее:

Func<Animal, Turtle> fat = whatever;
Func<Mammal, Reptile> fmr = fat;

Это законно. fat может забрать любое животное, а вызывающий fmr обещает передать Mammal, что всегда равно Animal. Точно так же fmr требует, чтобы функция возвращала Reptile, а fat всегда возвращает Turtle, что является Reptile.

Этот вид преобразования называется вариантом конверсия. В частности, «Мне требуется любое животное, поэтому я приму млекопитающее», которое называется контравариантным преобразованием, а «Я возвращаю черепаху, чтобы удовлетворить вашу потребность в рептилии» - это ковариант * Преобразование 1020 *.

C# поддерживает ковариантные и контравариантные преобразования для обобщенных c типов только при следующих обстоятельствах:

  • Типом, о котором идет речь, является делегат или интерфейс.
  • Тип был помечен как безопасный для отклонения, и компилятор подтвердил, что он безопасен.
  • Все аргументы типа, которые могут быть разными, являются ссылочными типами .

Вы удовлетворяете первым двум условиям - ваш Func<string, object> является типом делегата, который, как известно, безопасен для контравариантности в аргументе и ковариантности в возвращаемом. Однако вы не соответствуете третьему условию. bool? DateTime?, double? и int? являются не ссылочными типами . Следовательно, правила преобразования вариантов не применяются, и преобразования являются незаконными.

Это будет включать преобразование из Func<string, bool?> в Func<string, object>. Но это не то, что вы делаете; вы выполняете преобразование из группы методов , содержащей функцию, которая возвращает bool?. Это имеет значение?

Нет. Правила одинаковы для преобразований групп методов. Ковариантное преобразование из группы методов, содержащей метод, который возвращает bool? в Func<string, object>, недопустимо, поскольку bool? равно все еще , необходимому для того, чтобы сделать этот тип ссылки допустимым.

Независимо от того, как вы его нарезаете, вы застряли. Вы не можете преобразовать метод или делегат, который возвращает bool? напрямую , в делегат, который возвращает object.

Причина этого хорошо изложена в комментарии Йероена к этому вопросу. Где преобразование в бокс go? ​​ Преобразование в бокс код, который выделяет кучу памяти , и, следовательно, этот код должен go где-то , но есть компилятору некуда его поместить и по-прежнему поддерживать ссылочный идентификатор делегата.

Как вы правильно заметили, если вы предоставляете свою собственную лямбду, то компилятору есть место для размещения преобразования в бокс; это прикрепляет это сверху возвращения от лямбды. Ценой, которую вы платите за это, является то, что лямбда теперь является делегатом, который ссылается на тело лямбды , а не прямо на GetBooleanValue, так что здесь есть крошечный штраф за косвенное обращение.

...