Расширение Convert.ChangeType для создания пользовательских типов по запросу

Учитывая класс:

public class Foo
{
    public string Name { get; set; }
}

Можно ли создать экземпляр Foo из строки через Convert.ChangeType:

Type type = typeof(Foo);
object value = "one";

value = System.Convert.ChangeType(value, type);

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

Foo foo = new Foo() { Name = "one" };
string fooAsString = foo;  // implicit conversion -- no cast needed

Есть ли способ создать объект таким образом? Кроме того, у меня есть возможность изменить Convert.ChangeType, если есть другой способ сделать это.

Update: Причина, по которой я спрашиваю, заключается в том, что она выбрасывает и исключает:

Недействительный отбор из 'System.String' в 'JibbaJabba + Foo'.

и добавление оператора не решило проблему.

Ответы

Ответ 1

В соответствии с документацией MSDN:

Для успешной конверсии значение должен внедрить IConvertible интерфейса, потому что метод просто завершает вызов соответствующему IConvertible. Метод требует, чтобы преобразование ценности в Поддерживается тип преобразования.

Глядя на интерфейс IConvertible, он имеет метод ToType. Вы могли бы попробовать это, может быть? (Отказ от ответственности: у меня нет. Это просто мысль.)

Изменить. В вашем случае кажется, что вы хотите конвертировать из string в Foo. Поскольку тип string (очевидно) не определяет преобразование в Foo в своей реализации IConvertible, я считаю, что вам не повезло.


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

Я просмотрел код Convert.ChangeType в Reflector. Это долго; Я не буду воспроизводить его здесь. Но в основном это делает, как говорится в документации: он работает только если:

  • Параметр value - это непустой экземпляр типа, реализующего IConvertible, или:
  • Тип параметра value и параметр conversionType совпадают (так: Convert.ChangeType(myFoo, typeof(Foo)) также будет работать, хотя это будет бесполезно).

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

Итак, нам нужно посмотреть на реализацию string типа ToType.

К сожалению, это одна неудачная строка:

return Convert.DefaultToType(this, type, provider);

Что делает DefaultToType? Точно так же, как ChangeType (минус резерв ToType, очевидно, чтобы избежать бесконечной рекурсии).

Так что это просто не сработает.

Если вы абсолютно привязаны к этой сторонней библиотеке, которая использует Convert.ChangeType за кулисами, я бы рекомендовал обратиться к разработчику библиотеки и попросить их расширить их API в некотором роде, что позволит вам выполнить то, что вы пытаясь выполнить. Возможны следующие варианты:

  • Принятие необязательного параметра делегата Converter<string, T> или Func<string, T>, как было предложено Беном Войгтом в комментарии.
  • Принятие параметра TypeConverter
  • Принятие параметра некоторого типа, реализующего интерфейс типа IParser<T>

В любом случае, удачи.

Ответ 2

Прямой отбор из строки не будет работать, как уже указывал Дэн Тао. Могли бы вы, возможно, реализовать свою собственную оболочку для строки и использовать ее? Что-то вроде

class MyString: IConvertible {

  public string Value { get; set; }

  ...
  object IConvertible.ToType(Type conversionType, IFormatProvider provider) {
    if (conversionType == typeof(Foo))
      return new Foo { Name = Value };
    throw new InvalidCastException();
  }

}
...
MyString myString = new MyString{Value="one"};
Foo myFoo = (Foo)Convert.ChangeType(myString, typeof(Foo));

Не знаю, полезная ли это идея, но в любом случае..

Ответ 3

Неявный оператор здесь не работает? Если Foo - класс, который вы можете изменить, то я использовал что-то подобное в прошлом, что также позволяет сравнивать экземпляры Foo с строкой.

public class Foo
{
  public string Name { get; set; }

  public static implicit operator Foo(string value)
  {
    return new Foo { Name = value };
  }
}

...
Foo foo = "fooTest";
Console.WriteLine("Foo name: {0}", foo.Name);
...

Изменить: Если вы должны использовать ChangeType, насколько я знаю, вам не повезло. Если вы можете изменить API для использования TypeConverter, вы можете использовать что-то вроде следующего.

...
Type type = typeof(Foo);
object value = "one";

var converter = TypeDescriptor.GetConverter(type);
if (converter.CanConvertFrom(value.GetType()))
{
  object newObject = converter.ConvertFrom(value);
  Console.WriteLine("Foo value: {0}", newObject.ToString());
}
...

public class FooConverter : TypeConverter
{
  public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
  {
    return sourceType == typeof(string);
  }

  public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
  {
    var name = value as string;
    if (name != null)
      return new Foo { Name = name };
    else
      return base.ConvertFrom(context, culture, value);
  }
}

[TypeConverter(typeof(FooConverter))]
public class Foo
{
  public string Name { get; set; }

  public override string ToString()
  {
    return Name;
  }
}

Ответ 4

значение объекта1 = "один";

Значение Foo2 = (Foo) System.Convert.ChangeType(значение, typeof (Foo));

Edit:


Хорошо, если вы пытаетесь создать тип Foo из строки, вы можете использовать отражение:

String controlToCreate = "Foo";
Type typeofControl = Type.GetType(controlToCreate,true);