Приведение упакованного объекта обратно к исходному типу

#c# #generics #reflection #event-handling #boxing

#c# #обобщения #отражение #обработка событий #упаковка

Вопрос:

Я ожидаю, что на это есть один из двух ответов, либо невозможный, либо чрезвычайно простой, и я упустил из виду очевидный запрос Google.

Основная проблема заключается в том, что у меня есть универсальный объект, передаваемый через EventHandler , который помещает объект в коробку и затемняет истинный тип; только во время выполнения я знаю, что это за объект.

По общему признанию, dynamic ключевое слово может обойти проблему, но я бы хотел не потерять IntelliSense и все остальное, если смогу этого избежать. Кроме того, это не решает проблему, не зная, каковы каждое из свойств универсального объекта, без большого количества размышлений.

РЕДАКТИРОВАТЬ: Идея состоит в том, чтобы иметь возможность определять истинный тип объекта в параметре метода, а затем приводить этот объект к его истинному типу, не зная его заранее. Это всего лишь упрощенный пример. Возможно, термин «Упакованный» был неправильным.

Пример:

 public class Program
{
    static void Main(string[] args)
    {
        var container = new Container<Containee>(
            new Containee
            {
                Property1 = Guid.NewGuid(),
                Property2 = "I'm a property!",
                Property3 = DateTime.Now
            }
        );

        var boxed = (object)container;

        var originalType = boxed.GetType();

        // DOES NOT COMPILE: would like an operation like this
        // EDIT: Request for more detail
        var actualType = boxed as originalType;
        actualType.Entity.Property2 = "But I like this better.";
    }
}

public class Containee
{
    public Guid Property1 { get; set; } 
    public string Property2 { get; set; }
    public DateTime Property3 { get; set; }
}

public class Container<T>
{
    public Container(T entity)
    {
        Entity = entity;
    }

    public T Entity { get; internal set; }
}
  

Очевидно, что это не будет компилироваться, поскольку на самом деле нет способа привести в качестве переменной. Однако я надеюсь, что есть способ получить ссылку на фактический объект и тип или, по крайней мере, способ динамически воссоздавать тип.

Я ожидаю, что есть что-то простое, что я упускаю из виду, или лучший способ обойти это в целом. Суть в том, чтобы иметь возможность обернуть любой объект в контейнер, а позже выяснить, что это было.

Комментарии:

1. Я не вижу никакого бокса в вашем коде. Вам нужно сделать var actualType = boxed as Container<Containee>; , чтобы сделать его компилируемым. `

2. Упаковка классов не происходит. В упакованном виде содержатся только типы значений (int и т.д.). Слово, которое вы ищете, это casting . Вы привели упакованный объект к объекту.

3. Я думаю, что пример кода мешает. Он упоминает обработчик события, так что думайте об этом как о получении object sender . Он хочет знать, есть ли какой-либо способ во время компиляции вернуть реальный тип и воспользоваться преимуществами intellisense.

4. @Alex: Это не упаковка. @Richard прав; упаковка создает оболочку, на которую ссылается, чтобы тип значения мог быть сохранен в куче.

5. Я не думаю, что вы сталкиваетесь с этим ясно. Вы говорите, что не хотите «потерять IntelliSense и все остальное», но если вы не знаете тип переменной на момент написания кода, что вам дает intellisense? Вы не можете привести объект к неизвестному типу, поскольку это не имело бы практического эффекта.

Ответ №1:

Идея состоит в том, чтобы иметь возможность определять истинный тип объекта в параметре метода

Это достаточно просто (и вы уже это делаете).

 Type actualType = param.GetType();
  

Это даст вам фактический конкретный тип объекта

а затем приведение этого объекта к его истинному типу

Здесь все немного сходит с рельсов. Оператор приведения в C # (использование которого люди называют «приведением») может выполнять две вещи:

  1. Использование конкретных типов явное преобразование для создания нового объекта путем применения преобразования существующего объекта (обратите внимание, что это новая ссылка, которая создается; оригинальный тип объекта никогда не изменяется)
  2. Позволяет разработчику ссылаться на объект как на тип, который находится на другом уровне в его иерархии наследования, чем предоставляется в настоящее время (или интерфейс, который реализован для типа, который находится ниже в иерархии, чем на который в настоящее время ссылается)

В вашем случае первый вариант является правильным; оператор приведения, как и все операторы, не является полиморфным. То есть оператор применяется только в том случае, если он определен для типа, на который ссылаются, а не для объекта, на который ссылаются. Если вы хотите получить дополнительные разъяснения по этому поводу, дайте мне знать, но я не думаю, что это имеет отношение к вашему вопросу, поэтому я не собираюсь вдаваться в подробности, если меня не спросят.

Второй вариант — единственный вариант, который может быть реально применим к вам, но рассмотрите только две причины, по которым вы хотели бы это сделать:

  1. Чтобы вы могли ссылаться на объект как на определенный конкретный тип, который находится на более низком уровне, чем предоставляется в настоящее время (в вашем случае ваш объект является object , так что это почти такой же высокий уровень, как и он есть)
  2. Чтобы вы могли ссылаться на объект как на тип, который находится выше в иерархии, чтобы вы могли обходить скрытые (но не переопределяемые) члены.

(Подавляющее большинство приведений выполняется по причине # 1)

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

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

Комментарии:

1. на практике это не то, что делает GetType. в итоге я нашел это в поиске Google, потому что GetType возвращает «ListViewItem», но когда я смотрю на указанный элемент в отладчике, он говорит «CustomListViewItem»

Ответ №2:

Прежде всего: это не «упаковка». Упаковка предназначена для типов значений, таких struct как s.

Во-вторых: то, что вам, вероятно, нужно, это либо:

  • Отражение во время компиляции, которого нет в C #
  • Динамическая генерация кода, которую вы можете выполнить (болезненно) с Reflection.Emit .

В-третьих: ваш пример кода делает variable1 as variable2 , что на самом деле не имеет смысла. : Что вы собираетесь делать после этого? Возможно, есть способ получше.

Ответ №3:

Вы могли бы использовать dynamic :

 dynamic actualType = boxed;
actualType.Entity.Property2 = "But I like this better.";
  

Это должно скомпилироваться и сработать.

Ответ №4:

 var actualType = boxed as originalType;
  

Просто чтобы мы были на одной странице, позвольте мне объяснить, почему это невозможно.

var это конструкция времени компиляции. Это идентично немедленному объявлению переменной с правильным типом. Помимо того, что его проще вводить, он в основном используется для анонимных типов, которые, как подразумевается, не имеют имен.

В любом случае, чтобы добраться до сути вашего вопроса, лучше всего использовать динамическую генерацию кода либо с помощью Reflection.Emit , либо CodeDom (последнее намного проще понять, если вы не знаете ILASM, но работает намного медленнее).

В зависимости от того, что вы на самом деле хотите сделать, вам может сойти с рук что-то вроде

 if(someObject is Container<Containee>) {
     var container = (Container<Containee>)someObject;
     //...
}
  

Но, если вы можете ожидать буквально любого типа, что ж… удачи.

Комментарии:

1. Хотя ваши замечания о том, почему приведение переменной к переменной невозможно, очевидно, верны, я думаю, что последняя часть сообщения (другими словами, мясо) преждевременна; в OP неясно, что он хочет сделать , поэтому я не уверен, что кто-то еще в состоянии предложить предложения о том, как это сделать 😉

2. @Adam, Depending on what you actually want to do я включил это не просто так 😉

Ответ №5:

Основная проблема заключается в том, что у меня есть универсальный объект, передаваемый через обработчик событий, который помещает объект в коробку и затемняет истинный тип; только во время выполнения я знаю, что это за объект.

Как вы хотите с этим справиться, если тип известен только во время выполнения? Вы не можете вызывать какие-либо конкретные методы класса, потому что вы все равно не будете знать точный тип, если только все объекты не используют некоторый набор методов, которые могут быть извлечены как интерфейс.

В принципе, у вас есть несколько вариантов:

  • Используйте is и делайте разные вещи для разных типов:

     object value = GetValue ();
    if (value is Program)
        ((Program)value).Run ();
    else if (value is Animal)
        ((Animal)value).Run ();
      
  • Если предполагается, что все возможные типы должны совместно использовать набор операций, используйте интерфейс:

     object value = GetValue ();
    IRunnable runnable = (IRunnable)value;
    runnable.Run ();
      
  • Перефразируйте свой вопрос и дополните свой пример тем, как вы видите его работу после того, как вы выполнили ‘волшебное приведение’. Это дало бы нам представление о том, чего вы пытаетесь достичь.

Комментарии:

1. Это кажется наиболее близким к первоначальному замыслу, IMO.