Как преобразовать переменную определенного типа в общий T в Delphi?

В зависимости от фактического типа T я хочу вернуть определенное значение; здесь упрощенный пример (обертка записи вокруг некоторого объекта):

function TMyRec.TryGet<T>(const Default: T): T;
begin
  if TypeInfo(T) = TypeInfo(TMyObject) then
    Result:= FMyObject {E2010 Incompatible types}
  else Result:= FMyObject.TryGet<T>(Default);
end;

Понятно, что я не могу присвоить Result ничего, кроме T, а as T или T() не работают, когда T не ограничивается class...

Также невозможно выполнить перегрузку, которая отличается только общим ограничением:

function TryGet<T: TMyObject>(const Default: T): T; overload;
{...}
Result:= FMyObject as T; {here it would work}

Чтобы помочь понять обсуждение в комментариях ниже, вот пример до моего редактирования:

function TMyRec.TryGet<T>(const Default: T): T;
begin
  if TypeInfo(T) = TypeInfo(TMyObject) then Result:= FMyObject
  //else ...
  else Result:= Default;
end;

person maf-soft    schedule 11.05.2018    source источник
comment
Если T помечен как класс TryGet<T: class>, вы можете сделать MyObject as T в Delphi XE и новее. Затем вы также можете выполнить T(MyObject) в Delphi 2010 и новее.   -  person gabr    schedule 11.05.2018
comment
Это правда - забыл упомянуть, что мне это нужно не только для объектов... Буду редактировать.   -  person maf-soft    schedule 11.05.2018
comment
Самый простой способ - тот, который вы использовали в ответе, ИМХО.   -  person gabr    schedule 11.05.2018
comment
Ответ: это зависит. Существует множество различных возможных преобразований для различных комбинаций типов. Вам нужно решить, что такое спецификация.   -  person David Heffernan    schedule 11.05.2018
comment
Я просто сошлюсь на Эрика Липперта здесь, но вы можете подумать, что общее решение в этом случае, вероятно, неправильное решение. .   -  person J...    schedule 11.05.2018
comment
@J..., мне нравится и я согласен с Эриком Липпертом, и хорошо упомянуть об этом здесь. Каждый, у кого есть один и тот же вопрос, должен сначала подумать дважды. Однако иногда дженерики используются только для удобства ленивых пользователей класса, и в этом случае я могу отказаться от оптимальной методологии. Представьте, что вам пришлось написать что-то вроде TValue без таких компромиссов :) - не стесняйтесь писать более полный ответ, я бы с удовольствием его принял.   -  person maf-soft    schedule 11.05.2018
comment
@maf-soft Я не могу написать вам ответ, потому что понятия не имею, какую проблему вы на самом деле пытаетесь решить. Все, что я могу сказать, это то, что, учитывая то, как вы пытаетесь решить эту проблему, дженерики, вероятно, не являются правильным решением.   -  person J...    schedule 11.05.2018
comment
@DavidHeffernan, спецификация находится в примере кода: тип - T без ограничений, а присваиваемое значение - объект. Но, конечно, было бы интересно узнать, какие другие случаи требуют особого внимания.   -  person maf-soft    schedule 11.05.2018
comment
@J... - Я думаю, ты слишком много пытаешься читать между строк. Поскольку я уже сам опубликовал ответ, вы можете видеть, как для меня будет выглядеть приемлемый ответ. Вопрос только в том, как мне заставить работать Result:= FMyObject? Я не записал всю справочную информацию, потому что мой вопрос был именно об этом. Я не могу изменить концепцию своей структуры классов из 2000+ строк. И если вы не заметили: у меня уже есть свое решение — я записал его, чтобы помочь другим после того, как не смог найти существующее решение здесь, в stackoverflow.   -  person maf-soft    schedule 11.05.2018
comment
Это, кажется, не имеет большого смысла для меня. Если вы знаете, что это за типы, почему вы используете дженерик. Я в замешательстве, думаю, ты тоже.   -  person David Heffernan    schedule 11.05.2018
comment
Я согласен с @David: если вы должны использовать лестницу if-else или аналогичную (например, операторы case) для возврата значения, вы не используете дженерики. Тогда вы с тем же успехом можете использовать перегруженные методы.   -  person Rudy Velthuis    schedule 12.05.2018
comment
Да, полный и хороший ответ должен включать все эти опасения и предложения в дополнение к прямому ответу на вопрос. Да, разработчикам с одним и тем же вопросом следует переосмыслить свою концепцию и возможно решить ее по-другому. Но это был не вопрос, и я считаю, что такие опасения не должны приводить к тому, что вопрос или правильные ответы занижаются.   -  person maf-soft    schedule 12.05.2018
comment
@RudyVelthuis, давайте представим на секунду, что у меня есть причины, по которым вы все могли бы согласиться, если бы знали их. Как я должен был задать этот простой вопрос в общих чертах, не объясняя всей подоплеки? Код является просто примером, чтобы лучше понять вопрос в заголовке. Я не хочу подробно объяснять свой особый случай, потому что я уже решил его сам, и намерение опубликовать здесь состояло в том, чтобы помочь другим, ищущим тот же вопрос. Когда я не могу найти здесь свой вопрос, я его публикую, а когда сам нахожу ответ, я его тоже публикую. Я думаю, это то, что должен делать каждый.   -  person maf-soft    schedule 12.05.2018
comment
Сюрприз, я отредактировал пример, чтобы он больше походил на то, что я делаю на самом деле. Кажется, мое упрощение не было упрощением, теперь оно еще короче :) @RudyVelthuis   -  person maf-soft    schedule 12.05.2018
comment
Ваш вопрос все еще не ясен. Несколько конкретных примеров не помешали бы.   -  person David Heffernan    schedule 12.05.2018


Ответы (3)


Я считаю, что это лучший метод (по крайней мере, с точки зрения производительности) для достижения того, чего вы хотите.

type 
  PMyObject = ^TMyObject;

function TMyRec.TryGet<T>(const Default: T): T;
begin
  if TypeInfo(T) = TypeInfo(TMyObject) then 
    PMyObject(@Result)^ := FMyObject
  else if TypeInfo(T) = TypeInfo(Integer) then 
    PInteger(@Result)^ := 1
  //else ...
  else
    Result := Default;
end;
person Ken Bourassa    schedule 11.05.2018
comment
Отлично, так что TValue - не единственный способ, полезно знать альтернативу. Почему вы считаете, что это лучший метод? Для меня это выглядит как взлом. Однако я не понимаю, почему кто-то уже проголосовал за это, это добавляет ценности и полезно. Спасибо. Противники всегда должны публиковать свою причину. - person maf-soft; 11.05.2018
comment
@maf Это много раз обсуждалось в мете, голосование анонимно. Вам не нужно оправдывать то, как вы голосуете. Вверх или вниз. Это действительно плохой ответ. С двумя голосами "за". Я хотел бы знать, как за это можно было проголосовать. - person David Heffernan; 12.05.2018
comment
@David: что в этом плохого? Это более или менее то, что я тоже думал о публикации, когда я был вдали от своей установки Delphi: не приводить значение результата к T, приводить Result к типу (косвенно). Это не то, как вы обычно должны использовать дженерики, но иногда может произойти такая ситуация. И ИМО предпочтительнее RTTI. И да, это, конечно, взлом. В общем, не надо так бросать. Обычно вы либо не используете дженерики, либо не выполняете кастинг. - person Rudy Velthuis; 12.05.2018
comment
Конечно, иногда вы не хотите использовать Rtti в разделе uses или вам не нравятся (небольшие) накладные расходы. Так что хорошо знать, что я могу сделать это таким образом. Но для меня Rtti выглядит менее хакерским и более читабельным, поэтому я решил предпочесть его на этот раз. - person maf-soft; 12.05.2018
comment
Что делать, если тип является производным от TMyObject? Или это другой интегральный тип? Это плохой метод, неправильно реализованный. - person David Heffernan; 12.05.2018
comment
@maf-soft Я пояснил, что имел в виду. Что касается производительности, это будет НАМНОГО лучше, чем использование TValue. Даже если у вас будет много if/else, компилятор обычно оптимизирует их и оставит только присваивание. - person Ken Bourassa; 14.05.2018

Единственный способ, который я нашел, - это использовать System.Rtti.TValue (исключение, если неправильный тип):

Result:= TValue.From(FMyObject).AsType<T>

или намного короче ;-) с автоматическим типом (возвращает false, если тип неправильный; вы можете игнорировать это здесь, потому что вы уже проверили TypeInfo):

TValue.From(FMyObject).TryAsType(Result)

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

person maf-soft    schedule 11.05.2018
comment
Пожалуйста, не задавайте вопросы в ответах. - person David Heffernan; 11.05.2018
comment
Я думаю, что это решение довольно хорошее, и я буду его использовать, но что, если мне не нравится использовать TValue - есть ли другой умный способ? Ждем дополнительных ответов... - person maf-soft; 11.05.2018
comment
@DavidHeffernan, @J, я просто хотел призвать других публиковать дополнительные ответы, если есть другие или лучшие интересные решения, о которых я не знаю. Это был не настоящий вопрос. Настоящий вопрос в вопросе, а хороший ответ в ответе, чтобы помочь людям с той же проблемой. То, что ты делаешь, это придирки. Не каждый вопросительный знак является серьезным преступлением. Во всяком случае, я удалил его... - person maf-soft; 11.05.2018

Я использую Delphi XE5.

Измените образец yoru на это:

function TryGet<T: class>(const Default: T): T; overload;
{...}
Result:= FMyObject as T; {here it will work}

Изменение TMyObject на class. Когда вы не "класс", вы получите ошибку компилятора E2089.

person Bernd Ott    schedule 25.02.2020
comment
Но требование состоит в том, что он должен работать для любого типа. Это тоже уже обсуждалось в комментариях. - person maf-soft; 25.02.2020
comment
@maf-soft, ты прав. Все ищут ответы. Я тоже. Я не читаю комментарии. Я также упустил пункт типа any, потому что первое предложение было (обертка записи вокруг некоторого объекта). Примечание для меня: читайте внимательнее. - person Bernd Ott; 02.03.2020
comment
Для меня это нормально, когда вы просто удаляете этот ответ :) - person maf-soft; 02.03.2020