简体   繁体   中英

Convert System.String generically to any complex type using “Convert.ChangeType()”

I try to generically convert user input into either simple or complex types:

class Program
{
  static void Main(string[] args)
  {
    Console.WriteLine("Welcome, please provide the following info... Confirm with <RETURN>!");
    Console.WriteLine();    

    Console.Write("Name (e.g. 'Peggy Sue'): ");
    var user = GetUserInput<User>(Console.ReadLine());

    Console.WriteLine();
    Console.WriteLine();
    Console.WriteLine("Hi {0}, nice to meet you!", user.Forename);
    Console.WriteLine();

    Console.Write("Age: ");
    user.Age = GetUserInput<ushort>(Console.ReadLine());

    Console.WriteLine();
    Console.WriteLine("Thanks and goodbye!");
    Console.WriteLine("Press <RETURN> to quit...");
    Console.ReadLine();
  }

  static T GetUserInput<T>(string data)
  {
    return (T) Convert.ChangeType(data, typeof (T));
  }
}

class User
{
  public User(string name)
  {
    var splitted = name.Split(' ');
    Forename = splitted[0];
    Lastname = splitted[1];
  }

  public static implicit operator User (string value)
  {
    return new User(value);
  }

  public static explicit operator string (User value)
  {
    return string.Concat(value.Forename, " ", value.Lastname);
  }

  public string Forename { get; private set; }
  public string Lastname { get; private set; }

  public ushort Age { get; set; }
}

For the conversion to my "User" class, I always get the exception "Invalid cast from 'System.String' to 'ConsoleApplication1.User'.". Does anyone know how to fix this?

If I try something like this (not generically), it works just perfect:

Console.WriteLine((string) ((User) "Peggy Sue"));

No, Convert.ChangeType only works with a fixed set of types, I believe... or if the original object implements IConvertible , it can call IConvertible.ToType . That means you could implement IConvertible in your User class and have

Convert.ChangeType(user, typeof(string))

working, but that won't work the other way round.

Do you have a fixed set of types you need to convert? If so, you could have a Dictionary<Type, Func<string, object>> which you'd populate with conversion delegates. Then you just need to call the appropriate conversion and cast the return value. It's ugly, but probably your best bet.

One option here might be to associate a TypeConverter with the types you care about (you can do this at compile-time via [TypeConverter(...)] , or there is a trick for doing this at runtime if you don't control the types).

Then it is:

TypeConverter conv = TypeDescriptor.GetConverter(typeof(T));
T obj = (T)conv.ConvertFromString(text); // or ConvertFromInvariantString

I fixed it. Check this:

class Program
{
  static void Main(string[] args)
  {
    Console.WriteLine("Welcome, please provide the following info... Confirm with <RETURN>!");
    Console.WriteLine();

    Console.Write("Name (e.g. 'Peggy Sue'): ");
    var user = GetUserInput<User>(Console.ReadLine());

    Console.WriteLine();
    Console.WriteLine();
    Console.WriteLine("Hi {0}, nice to meet you!", user.Forename);
    Console.WriteLine();

    Console.Write("Age: ");
    user.Age = GetUserInput<ushort>(Console.ReadLine());

    Console.WriteLine();
    Console.WriteLine("Thanks and goodbye!");
    Console.WriteLine("Press <RETURN> to quit...");
    Console.ReadLine();
  }

  static T GetUserInput<T>(string data)
  {
    TypeConverter conv = TypeDescriptor.GetConverter(typeof(T));
    return (T) conv.ConvertFromInvariantString(data);
  }
}

[TypeConverter(typeof(UserConverter))]
class User
{
  public User(string name)
  {
    var splitted = name.Split(' ');
    Forename = splitted[0];
    Lastname = splitted[1];
  }

  public static explicit operator User (string value)
  {
    return new User(value);
  }

  public static explicit operator string (User value)
  {
    return string.Concat(value.Forename, " ", value.Lastname);
  }

  public string Forename { get; private set; }
  public string Lastname { get; private set; }

  public ushort Age { get; set; }
}

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

  public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
  {
    if (value is string)
    {
      return (User)(value as string);
    }

    return null;
  }
}

If you're looking to convert to numeric types, I prefer the much briefer and intent-exposing:

decimal.Parse(someString)

Or, in your example:

new User(userName)

No reason to create an entire method (or class, if you decide in the future to "make this reusable") just to wrap a cast. This is especially true when the language already has a less opaque way to express the intent of your code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM