web-dev-qa-db-fra.com

Analyse de chaîne générique C # à n'importe quel objet

Je stocke des valeurs d'objet dans des chaînes, par exemple,

string[] values = new string[] { "213.4", "10", "hello", "MyValue"};

existe-t-il un moyen d'initialiser génériquement les types d'objet appropriés? par exemple, quelque chose comme

double foo1 = AwesomeFunction(values[0]);
int foo2 = AwesomeFunction(values[1]);
string foo3 = AwesomeFunction(values[2]);
MyEnum foo4 = AwesomeFunction(values[3]);

AwesomeFunction est la fonction dont j'ai besoin. L’utilisation finale consiste à initialiser des propriétés, par exemple,

MyObject obj = new MyObject();
PropertyInfo info = typeof(MyObject).GetProperty("SomeProperty");
info.SetValue(obj, AwesomeFunction("20.53"), null);

La raison pour laquelle j'ai besoin de cette fonctionnalité est que je stocke lesdites valeurs dans une base de données et souhaite les lire via une requête, puis initialiser les propriétés correspondantes d'un objet. Est-ce que ça va être possible? L'objet entier n'est pas stocké dans la base de données, mais quelques champs que j'aimerais lire et définir de manière dynamique. Je sais que je peux le faire de manière statique, mais cela va devenir fastidieux, difficile à maintenir, et sujet à des erreurs avec de nombreux champs/propriétés différents en cours de lecture.

EDIT: Les points bonus si AwesomeFunction peuvent fonctionner avec des classes personnalisées qui spécifient un constructeur qui prend une chaîne!

EDIT2: Le type de destination peut être connu via le PropertyType, dans le cas spécifique où je souhaite utiliser ce type de fonctionnalité. Je pense que Enums serait facile à analyser avec cela, par exemple,

Type destinationType = info.PropertyType;
Enum.Parse(destinationType, "MyValue");
27
mike

Peut-être que la première chose à essayer est:

object value = Convert.ChangeType(text, info.PropertyType);

Cependant, cela ne prend pas en charge l'extensibilité via des types personnalisés; si vous en avez besoin , que diriez-vous de:

TypeConverter tc = TypeDescriptor.GetConverter(info.PropertyType);
object value = tc.ConvertFromString(null, CultureInfo.InvariantCulture, text);
info.SetValue(obj, value, null);

Ou:

info.SetValue(obj, AwesomeFunction("20.53", info.PropertyType), null);

avec

public object AwesomeFunction(string text, Type type) {
    TypeConverter tc = TypeDescriptor.GetConverter(type);
    return tc.ConvertFromString(null, CultureInfo.InvariantCulture, text);
}
36
Marc Gravell
public T Get<T>(string val)
{
    if (!string.IsNullOrWhiteSpace(val))
        return (T) TypeDescriptor.GetConverter(typeof (T)).ConvertFromString(val);
    else 
        return default(T);
}
4
RouR

Voici une version simple:

object ConvertToAny(string input)
{
    int i;
    if (int.TryParse(input, out i))
        return i;
    double d;
    if (double.TryParse(input, out d))
        return d;
    return input;
}

Il reconnaîtra et double, mais tout le reste est renvoyé sous forme de chaîne. Le problème de la gestion des énumérations est qu’il n’ya aucun moyen de savoir à quelle enum une valeur appartient et qu’il est impossible de savoir si elle doit être une chaîne ou non. D'autres problèmes sont qu'il ne gère pas les dates/heures ou les décimales (comment les distingueriez-vous des doubles?), Etc.

Si vous êtes prêt à changer votre code comme ceci:

PropertyInfo info = typeof(MyObject).GetProperty("SomeProperty"); 
info.SetValue(obj, AwesomeFunction("20.53", info.PropertyType), null); 

Cela devient alors beaucoup plus facile:

object ConvertToAny(string input, Type target)
{
    // handle common types
    if (target == typeof(int))
        return int.Parse(input);
    if (target == typeof(double))
        return double.Parse(input);
    ...
    // handle enums
    if (target.BaseType == typeof(Enum))
        return Enum.Parse(target, input);
    // handle anything with a static Parse(string) function
    var parse = target.GetMethod("Parse",
                    System.Reflection.BindingFlags.Static |
                    System.Reflection.BindingFlags.Public,
                    null, new[] { typeof(string) }, null);
    if (parse != null)
        return parse.Invoke(null, new object[] { input });
    // handle types with constructors that take a string
    var constructor = target.GetConstructor(new[] { typeof(string) });
    if (constructor != null)
        return constructor.Invoke(new object[] { input });
}

Edit: Ajout d'une parenthèse manquante

3
Gabe

Je sais que cela ne répond pas à votre question, mais avez-vous examiné Dapper micro ORM ?
C'est très simple (comparé à LINQ to SQL ou, pour cette raison, à Entity Framework) et fait ce que vous voulez.

Considère ceci:

public class Dog
{
    public int? Age { get; set; }
    public Guid Id { get; set; }
    public string Name { get; set; }
    public float? Weight { get; set; }    
}            

var guid = Guid.NewGuid();
var dog = connection.Query<Dog>("select * from Dogs where Id = @Id",
    new { Id = 42 }
).First();

Dapper lui-même est emballé dans un fichier unique et, apparemment, est utilisé par l'équipe StackOverflow (à l'exception de Linq en SQL).

0
Dan Abramov