Почему этот TypeConverter не работает?

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

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

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

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

- edit - Похоже, что это не работает из-за некоторого причуда загрузки сборок динамически - этот код работает под плагиновой архитектурой.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.ComponentModel;

namespace MyTest
{

    public class TestTester
    {
        public static void Main(string[] args)
        {
            object v = TypeDescriptor.GetConverter(typeof(MyTest.Test)).ConvertFromInvariantString("Test");
        }
    }

    public class TestConverter : TypeConverter
    {

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

        public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType)
        {
            if (sourceType == typeof(string) || base.CanConvertFrom(context, sourceType))
            {
                return true;
            }
            return base.CanConvertFrom(context, sourceType);
        }

        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            if (destinationType == typeof(Test) || base.CanConvertTo(destinationType))
            {
                return true;
            }
            return base.CanConvertTo(context, destinationType);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            if (value.GetType() == typeof(string))
            {
                Test t = new Test();
                t.TestMember = value as string;
                return t;
            }
            return base.ConvertFrom(context, culture, value);
        }

        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(string) && value.GetType() == typeof(Test))
            {
                return ((Test)value).TestMember;
            }
            return base.ConvertTo(context, culture, value, destinationType);
        }

    }

    [TypeConverterAttribute(typeof(TestConverter))]
    public struct Test
    {
        public string TestMember { get; set; }
    }
}

Ответ 1

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

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

public void DoMagic()
{
    // NOTE: After this, you can use your typeconverter.
    AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}

private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    AppDomain domain = (AppDomain)sender;
    foreach (Assembly asm in domain.GetAssemblies())
    {
        if (asm.FullName == args.Name)
        {
            return asm;
        }
    }
    return null;
}

Ответ 2

Ответ на этот другой вопрос должен быть применим здесь. Это гораздо более простое решение, чем подписка на AssemblyResolve.

Таким образом, идея состоит в том, чтобы установить атрибут TypeConverter используя полное строковое имя класса преобразователя типов, вместо использования typeof для предоставления имени класса.

Другими словами, вместо этого:

[TypeConverterAttribute(typeof(TestConverter))]
public struct Test
{
    ...
}

сделай это:

[TypeConverterAttribute("MyTest.TestConverter")]
public struct Test
{
    ...
}

Ответ 3

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

Единственное, что я могу сказать о том, что в сложном сценарии у вас может не быть разрешения Reflection.

Ответ 4

Это немного поздно, но эта проблема возникла, когда я попросил TypeConverter, который находится в другой сборке, на которую прямо не ссылается исполняемая сборка.

Ответ 5

Мы также наблюдали это поведение в подключаемых системах, включая загрузку сборок из-за пределов папки приложения.

Корень всего зла является недостатком реализации TypeDescriptorAttribute.

Атрибут имеет две перегрузки конструктора, одну для спецификации типа обычного текста (которая - не неожиданно - чистая магия во время выполнения) и одна для ссылки ранней привязки typeof(). Если вы используете второй путь, что может пойти не так? Ну, на самом деле атрибут использует только первый путь. Реальная и правильная ссылка типа времени выполнения сглажена в открытый текст, и здесь есть драконы. Поэтому не нужно писать typeof() - это всегда сценарий открытого текста и магии внутри.

Решение? Не идеальный, но в нашем случае мы потребляли только преобразования типов внутри нашей системы, поэтому мы выбрали ValueSerializerAttribute. Это в основном способ WPF делать одни и те же вещи. Его реализация правильна в отношении перегрузки typeof() .ctor в том, что ей удается сохранить идентификатор раннего типа и всегда загружает правильный тип, как написано в коде.

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