Динамический кастинг в .NET

blanko27
Уже с Приветом
Posts: 2264
Joined: 17 Jun 2003 04:41
Location: Just like US

Динамический кастинг в .NET

Post by blanko27 »

Ни как не могу наити ответ, как можно динамически откастить обьект по его типу.
Это работает:

Code: Select all

Dim obj As Object = CType(otherObject, MyClass)

Это не работает:

Code: Select all

Dim myClassType As Type = GetType(MyClass)
Dim obj As Object = CType(otherObject, myClassType )

В моем случае тип объекта (MyClass) заранее не известен и загружается динамически из внешнего ассембли, поэтому никак не могу понять - как откастить объект? :pain1:
...а мы такой компанией, возьмем, да и припремся к Элис!
Niky
Уже с Приветом
Posts: 550
Joined: 31 Mar 2000 10:01
Location: Moscow --> Baltimore, MD

Post by Niky »

Для простых случаев - Type.DefaultBinder.ChangeType, для более сложных можно написать свой MyBinder : Binder и тогда MyBinder.ChangeType.
blanko27
Уже с Приветом
Posts: 2264
Joined: 17 Jun 2003 04:41
Location: Just like US

Post by blanko27 »

Code: Select all

Dim obj As Object = Type.DefaultBinder.ChangeType(otherObject, otherObject.GetType, New System.Globalization.CultureInfo(""))

- у меня дает "ChangeType operation is not supported". Видимо сложный случай. :?
...а мы такой компанией, возьмем, да и припремся к Элис!
Bobo
Уже с Приветом
Posts: 518
Joined: 04 Jun 2002 01:40
Location: CA, USA

Post by Bobo »

1. Объясните, зачем кастить к типу, неизвестному в compile-time? Вы же не можете объявить переменную этого типа, зачем тогда к нему кастить?
2. Binder здесь ни при чем. Разумнее было предложить Convert.ChangeType(), но это тоже совсем не то.
3. btw не используйте CType для кастинга, это очень плохой стиль. Лучше всего не используйте CType вообще. Для кастинга есть DirectCast, а для конвертирования - Convert.
User avatar
Gennadiy
Уже с Приветом
Posts: 11332
Joined: 30 Mar 2000 10:01
Location: Ice Storm Town

Post by Gennadiy »

Пытаясь прочесть ваши мысли и предполагая, что вам просто надо вызывать методы, свойства неизвестного в run-time объекта, советую обратить внимание на System.Reflection.
blanko27
Уже с Приветом
Posts: 2264
Joined: 17 Jun 2003 04:41
Location: Just like US

Post by blanko27 »

Bobo wrote:1. Объясните, зачем кастить к типу, неизвестному в compile-time? Вы же не можете объявить переменную этого типа, зачем тогда к нему кастить?

Например, тип построен динамически, через type factory.
Bobo wrote:2. Binder здесь ни при чем. Разумнее было предложить Convert.ChangeType(), но это тоже совсем не то.

Да, спасибо, это у меня уже почти работает.
Bobo wrote:3. btw не используйте CType для кастинга, это очень плохой стиль. Лучше всего не используйте CType вообще. Для кастинга есть DirectCast, а для конвертирования - Convert.
OK :) Я вообще кастинг стараюсь не употреблять. Просто, здесь случай, когда объект полученный из внешнего ассембли в результате вызова Activator.CreateInstance(тип) не показывается правильно в Property Grid. Кастингом на известный тип я пытаюсь поправить ситуацию. Пока не очень получается - половина атрибутов класса присутствует (типа простых description, category), а половина - нет (сложные: custom editor, convertor) .
Еще, ко всему, у меня имеется проблема при серилизации/десерилизации этих объектов с помощью BinaryFormatter-а. При десерелизации простого объекта я получаю ошибку, что класс должен имплементировать IConvertible. А для более сложного, с атрибутами custom editor и converter, BinaryFormatter вообще говорит "Object type can not be converted to target type", и это в строчке:

Code: Select all

Dim newObj As Object = binFormatter.Deserialize(fileStream)

:pain1:
Last edited by blanko27 on 26 Mar 2004 10:33, edited 4 times in total.
...а мы такой компанией, возьмем, да и припремся к Элис!
blanko27
Уже с Приветом
Posts: 2264
Joined: 17 Jun 2003 04:41
Location: Just like US

Post by blanko27 »

Gennadiy wrote:Пытаясь прочесть ваши мысли и предполагая, что вам просто надо вызывать методы, свойства неизвестного в run-time объекта, советую обратить внимание на System.Reflection.
Нет, спасибо, методы пока я не затрагиваю.
...а мы такой компанией, возьмем, да и припремся к Элис!
Niky
Уже с Приветом
Posts: 550
Joined: 31 Mar 2000 10:01
Location: Moscow --> Baltimore, MD

Post by Niky »

1. Convert.ChangeType будет работать только для типов, имплементирующих IConvertable
2. Binder можно написать и для типов, которые об IConvertable понятия не имеют
3. ChangeType в DefaultBinder MS и правда не импрементировал - тут MSDN врет
4. Чтобы получить атрибуты или properties, кастинг не нужен. Это делается через Reflection, как и сказал Геннадий:

Code: Select all

   class Class1
   {
      [STAThread]
      static void Main(string[] args)
      {
         Type dpType = typeof(System.Windows.Forms.DateTimePicker);
         object[] customAttrs = dpType.GetCustomAttributes(true);
         for( int i = 0; i < customAttrs.Length; i++ )
         {
            object attr = customAttrs[i];
            Console.WriteLine("Attribute: {0}", attr);
            PrintProperties(attr);
         }
      }

      private static void PrintProperties(Object target)
      {
         Type targetType = target.GetType();
         PropertyInfo[] props = targetType.GetProperties();
         Console.WriteLine("  Properties:");
         for( int i = 0; i < props.Length; i++ )
         {
            PropertyInfo pInfo = props[i];
            Console.WriteLine("    {0} : {1}", pInfo.Name, pInfo.GetValue(target, null));
         }
         Console.WriteLine();
      }
   }
blanko27
Уже с Приветом
Posts: 2264
Joined: 17 Jun 2003 04:41
Location: Just like US

Post by blanko27 »

Niky wrote:4. Чтобы получить атрибуты или properties, кастинг не нужен. Это делается через Reflection, как и сказал Геннадий.
Так я думал, что функция Activator.CreateInstance() при создании типа полученного из ассембли с помощью GetTypes() автоматически должна добавить все его атрибуты и вручную добавлять их нет смысла, я ошибаюсь?
Например, я помещаю в ассембли класс такого вида:

Code: Select all

<Serializable()> _
Public Class MyClass

    Private m_results As MyClassResultsCollection

    <Description("MyClass results."), _
        Category("MyClass Data"), _
        TypeConverter(GetType(MyClassResultsCollectionConverter)), _
        Editor(GetType(MyClassResultsCollectionEditor), GetType(UITypeEditor))> _
    Public Property Results( _
        ) As MyClassResultsCollection
        Get
            Return m_results
        End Get
        Set(ByVal Value As MyClassResultsCollection)
            m_results = Value
        End Set
    End Property

End Class

Как видите, вместе с основным классом присутствуют custom editor MyClassResultsCollectionEditor и конвертер MyClassResultsCollectionConverter. Если показать этот класс в Property Grid того же самого проекта, где он и был продекларирован, то все нормально, виден + и ... (ellipses box) Если показать этот класс в Property Grid в проекте, где он создается с помощью Activator.CreateInstance(), то нет ни +, ни custom editor-а. Видимо эти вспомогательные классы должны каким-то образом быть сконструированы во время отображения объекта основного класса в Property Grid, но в какой момент и куда их добавить? :pain1:
...а мы такой компанией, возьмем, да и припремся к Элис!
Bobo
Уже с Приветом
Posts: 518
Joined: 04 Jun 2002 01:40
Location: CA, USA

Post by Bobo »

Боюсь, что у вас может быть серьезная проблема с type resolution.
Дело в том, что если тип загружен из другой ассембли, он может быть несовместим с тем же типом, обявленным в compile-time.
То есть, если десигнер идет по атрибутам вашего обьекта и ишет нужные по методу
"if attr is TheAttributeICareAbout", то етот "if" может всегда быть false.
Ето зависит от метода загрузки ассембли, от того, есть ли у нее strong name, лежит ли она в ГАКе.

В конкретной ситуации советую попробовать не использовать GetType в конструкторах атрибутов, а использовать string type names (там есть такие оверлоады)
blanko27
Уже с Приветом
Posts: 2264
Joined: 17 Jun 2003 04:41
Location: Just like US

Post by blanko27 »

Bobo wrote:...советую попробовать не использовать GetType в конструкторах атрибутов, а использовать string type names (там есть такие оверлоады)
Спасибо, я не знал об этих оверлоадах, обязательно проверю! :)
...а мы такой компанией, возьмем, да и припремся к Элис!
blanko27
Уже с Приветом
Posts: 2264
Joined: 17 Jun 2003 04:41
Location: Just like US

Post by blanko27 »

Bobo wrote:...если тип загружен из другой ассембли, он может быть несовместим с тем же типом, обявленным в compile-time.
То есть, если десигнер идет по атрибутам вашего обьекта и ишет нужные по методу
"if attr is TheAttributeICareAbout", то етот "if" может всегда быть false.
Ето зависит от метода загрузки ассембли, от того, есть ли у нее strong name, лежит ли она в ГАКе.
Мне кажется, я понимаю о чем вы говорите. Вопрос, можно ли на дизайнер повесить какие-то ивенты и по ним загружать требуемые классы например в AppDomain.CurrentDomain.CreateInstance ?
...а мы такой компанией, возьмем, да и припремся к Элис!
User avatar
Strannik223
Уже с Приветом
Posts: 569
Joined: 14 Dec 2003 04:06
Location: Львов->Киев->Торонто

Post by Strannik223 »

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

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

А вообще что то в консерватории не так, похоже на неправильную архитектуру.
Вы можете задачу описать более полно?
Никакой разрухи нет. (с) Проф. Преображенский.
blanko27
Уже с Приветом
Posts: 2264
Joined: 17 Jun 2003 04:41
Location: Just like US

Post by blanko27 »

Strannik223 wrote:Правильно ли я понял: вы хотите прочитав из потока (десериализовав) некоторый объект изменить его тип?
Не совсем. Разговор стал немного запутан, но в 2-х словах так:
1. Подгрузить тип из внешнего ассембли с (что важно) custom editor и converter аттрибутами.
2. Серелизовать/десерилизовать объект созданный из данного типа (в файл/ базу данных - не имеет значения).
Проблема: объект полученный таким образом теряет custom editor и converter аттрибуты. Кастингом я пытался поправить ситуацию, но такое тривиальное решение не работает, нужен как вы верно указали, кастомизировать type resolver. С сериализацией/десерелизацией пока тоже не ясно. :pain1:
...а мы такой компанией, возьмем, да и припремся к Элис!
blanko27
Уже с Приветом
Posts: 2264
Joined: 17 Jun 2003 04:41
Location: Just like US

Post by blanko27 »

Strannik223 wrote:А вообще что то в консерватории не так, похоже на неправильную архитектуру.
Не могу ручатся за общую архитектуру, мне задачу поставили именно так, теперь вот думаю. :)
...а мы такой компанией, возьмем, да и припремся к Элис!
User avatar
IA72
Уже с Приветом
Posts: 956
Joined: 04 Mar 2002 10:01

Post by IA72 »

blanko27 wrote:
Strannik223 wrote:Правильно ли я понял: вы хотите прочитав из потока (десериализовав) некоторый объект изменить его тип?
Не совсем. Разговор стал немного запутан, но в 2-х словах так:
1. Подгрузить тип из внешнего ассембли с (что важно) custom editor и converter аттрибутами.
2. Серелизовать/десерилизовать объект созданный из данного типа (в файл/ базу данных - не имеет значения).
Проблема: объект полученный таким образом теряет custom editor и converter аттрибуты. Кастингом я пытался поправить ситуацию, но такое тривиальное решение не работает, нужен как вы верно указали, кастомизировать type resolver. С сериализацией/десерелизацией пока тоже не ясно. :pain1:


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

Позже. Проверил - в элементарном случае не теряются и грид нормально распознает.


Код класса из сторонней сборки

Code: Select all

namespace ADLab.Test.Classes
{

   public class InputTypeConverter: StringConverter
   {
      public InputTypeConverter()
      {
      }

      public override bool GetStandardValuesSupported(
         ITypeDescriptorContext context)
      {
         return true;
      }

      public override StandardValuesCollection
         GetStandardValues(ITypeDescriptorContext context)
      {
         ArrayList values = new ArrayList();
         values.Add("Text");
         values.Add("Checkbox");
         values.Sort();
         return new StandardValuesCollection(values);
      }

      public override bool GetStandardValuesExclusive(
         ITypeDescriptorContext context)
      {
         return true;
      }
   }

   [DefaultPropertyAttribute("Element")]
   public class TestClass
   {
      
      public TestClass()
      {
      }

      
      [Browsable(true)]
      [CategoryAttribute("Edit")]
      [DefaultValue("Text")]
      [Description("Input type")]
      [TypeConverter(typeof(InputTypeConverter))]
      public string Input
      {
         get {return inputType;}
         set {inputType = value;}
      }
      private string inputType = "Text";
   }
}


Код приложения


Code: Select all

         Assembly assembly = Assembly.LoadFrom("ClassTest.dll");
         this.objectGrid.SelectedObject =
            assembly.CreateInstance("ADLab.Test.Classes.TestClass");


Все работает.
Вопрос - где у вас определены редакторы?
Вполне может быть конфликт версий, незаметных глазу.
Если они где-то в третьей сборке, и у нее выставлен автоматический инкремент версий, то при сборке главного приложения версия меняется, и грид не может найти старую, ту, на которую ссылаются ваши классы.
Внимательно посмотрите на все референсы.
blanko27
Уже с Приветом
Posts: 2264
Joined: 17 Jun 2003 04:41
Location: Just like US

Post by blanko27 »

IA72 wrote:Не должен терять, если память мне не изменяет. Сейчас проверю.
А вы руками у созданного объекта проверяли наличие этих атрибутов, может там дело не в терянии совсем?
Вы знаете, с помощью какой-то матери и отличной подсказки Bobo (попробовать не использовать GetType в конструкторах атрибутов) эта часть отлично заработала! Сейчас код выглядит что-то типа:

Code: Select all

<Serializable()> _ 
Public Class MyClass

    Private m_results As MyClassResultsCollection

    <Description("MyClass results."), _
        Category("MyClass Data"), _
        TypeConverter("MyLib.MyClassResultsCollectionConverter"), _
        Editor("MyLib.MyClassResultsCollectionEditor", GetType(UITypeEditor))> _
    Public Property Results( _
        ) As MyClassResultsCollection
        Get
            Return m_results
        End Get
        Set(ByVal Value As MyClassResultsCollection)
            m_results = Value
        End Set
    End Property

End Class

От последнего GetType() мне так и не удалось избавится, но это, как будто, уже не мешает.
Осталась проблема с серелизацией/десерелизацией. Как я писал раньше:
blanko27 wrote:При десерелизации простого объекта я получаю ошибку, что класс должен имплементировать IConvertible. А для более сложного, с атрибутами custom editor и converter, BinaryFormatter вообще говорит "Object type can not be converted to target type", и это в строчке:

Code: Select all

Dim newObj As Object = binFormatter.Deserialize(fileStream)

Это меня вообще ставит в тупик. Как так, нельзя получить значение в переменную типа Object??? Уж более общего типа вряд ли сыщешь. Причем, бесполезная команда (без присваивания)

Code: Select all

binFormatter.Deserialize(fileStream)

выполняется совершенно безболезненно!
...а мы такой компанией, возьмем, да и припремся к Элис!
User avatar
IA72
Уже с Приветом
Posts: 956
Joined: 04 Mar 2002 10:01

Post by IA72 »

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

Вот это простейший код у меня работает

Code: Select all

         Assembly assembly = Assembly.LoadFrom("ClassTest.dll");
         this.propertyGrid1.SelectedObject = assembly.CreateInstance("ADLab.Test.Classes.TestClass");
         XmlSerializer serializer = new XmlSerializer(propertyGrid1.SelectedObject.GetType());
         TextWriter writer = new StreamWriter("c:\\temp\\sr.xml");
         try
         {
            serializer.Serialize(writer, this.propertyGrid.SelectedObject);
         }
         finally
         {
            writer.Close();
         }
         FileStream fs = new FileStream("c:\\temp\\sr.xml", FileMode.Open);
         try
         {
            this.propertyGrid.SelectedObject = serializer.Deserialize(fs);
         }
         finally
         {
            fs.Close();
         }
User avatar
Strannik223
Уже с Приветом
Posts: 569
Joined: 14 Dec 2003 04:06
Location: Львов->Киев->Торонто

Post by Strannik223 »

blanko27 wrote:От последнего GetType() мне так и не удалось избавится, но это, как будто, уже не мешает.
Осталась проблема с серелизацией/десерелизацией. Как я писал раньше:
blanko27 wrote:При десерелизации простого объекта я получаю ошибку, что класс должен имплементировать IConvertible. А для более сложного, с атрибутами custom editor и converter, BinaryFormatter вообще говорит "Object type can not be converted to target type", и это в строчке:

Code: Select all

Dim newObj As Object = binFormatter.Deserialize(fileStream)

Это меня вообще ставит в тупик. Как так, нельзя получить значение в переменную типа Object??? Уж более общего типа вряд ли сыщешь. Причем, бесполезная команда (без присваивания)

Code: Select all

binFormatter.Deserialize(fileStream)

выполняется совершенно безболезненно!


Только сейчас понял...
"Такого быть не может" :)

Попробуйте вот что: попытайтесь создать тот тип как
Activator.CreateObject("My.project.namespace.MyObject, myProjectDll");
Если это не сработает, то десериализатор то же не будет работать потому что не хватает каких то ассембли
Никакой разрухи нет. (с) Проф. Преображенский.
User avatar
Strannik223
Уже с Приветом
Posts: 569
Joined: 14 Dec 2003 04:06
Location: Львов->Киев->Торонто

Post by Strannik223 »

blanko27 wrote:Проблема: объект полученный таким образом теряет custom editor и converter аттрибуты.


Не верю!

Объект не может потерять атрибуты потому что он их не имеет!
Объект содержит ссылку на свой тип(AKA class), а тип в свою очередь содержит атрибуты
Поэтому тип может потерять атрибуты только в одном случае: это какой-то другой тип...
Никакой разрухи нет. (с) Проф. Преображенский.
blanko27
Уже с Приветом
Posts: 2264
Joined: 17 Jun 2003 04:41
Location: Just like US

Post by blanko27 »

IA72 wrote:Внимательно посмотрите на все референсы.

Strannik223 wrote:Поэтому тип может потерять атрибуты только в одном случае: это какой-то другой тип...

Спасибо огромное всем, я нашел проблему! У меня стоял дополнительный обработчик, подцепленный к событию AppDomain.CurrentDomain.AssemblyResolve. В нем, вместо того, чтобы использовать Reflection.Assembly.LoadFrom, как и при создании типов подгружаемых из внешних ассембли, я почему-то пользовался Reflection.Assembly.Load, хотя ассембли, из которых подгружались типы не подписаны, и не в ГАС-е. Десерилизатор обращался к этому хэндлеру и получал референс на какой-то левый ассембли. В общем, полный бардак.
Еще раз всем спасибо! :)
...а мы такой компанией, возьмем, да и припремся к Элис!

Return to “Вопросы и новости IT”