2010-10-19 14 views
27

sto memorizzare i valori degli oggetti nelle stringhe per esempio,C# stringa generica parse a qualsiasi oggetto

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

Esiste un modo genericamente inizializzare i tipi di oggetti appropriati? per esempio, qualcosa come

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

dove AwesomeFunction è la funzione che ho bisogno. L'utilizzo finale è inizializzare proprietà esempio,

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

il motivo che devo tale funzionalità è sto memorizzare detti valori in un database, e desiderare di leggere loro tramite una query e poi inizializzare le corrispondenti proprietà di un oggetto . Sarà possibile? L'intero oggetto non viene memorizzato nel database, solo alcuni campi che mi piacerebbe leggere & impostati dinamicamente. So che posso farlo staticamente, ma che diventerà noioso, difficile da mantenere e soggetto a errori con numerosi campi/proprietà diversi.

MODIFICA: i punti bonus se AwesomeFunction possono funzionare con classi personalizzate che specificano un costruttore che accetta una stringa!

EDIT2: il tipo di destinazione può essere sapere tramite il PropertyType, nel caso specifico in cui voglio usare questo tipo di funzionalità. Penso enumerazioni sarebbe facile analizzare con questo esempio,

Type destinationType = info.PropertyType; 
Enum.Parse(destinationType, "MyValue"); 
+0

re "punti bonus" - sarebbe meglio definire e associare un 'TypeConverter', e avere * che * gestirlo (inoltro al costruttore). Poiché questo è quello che i convertitori di tipi sono progettati per ... –

risposta

35

Forse la prima cosa da provare è:

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

Tuttavia, questo non supporta l'estensibilità tramite tipi personalizzati; se avete bisogno di che, come su:

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

Oppure:

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

con

public object AwesomeFunction(string text, Type type) { 
    TypeConverter tc = TypeDescriptor.GetConverter(type); 
    return tc.ConvertFromString(null, CultureInfo.InvariantCulture, text); 
} 
+0

Wow, risposta veloce! Sembra che questo codice funzioni bene. Quella classe TypeConverter è piuttosto dolce, praticamente esattamente quello che cercavo. Grazie! – mike

+7

+ 1.PS: per la modifica dell'OP: se hai altre classi personalizzate oltre a int/double/etc, puoi scrivere un corrispondente TypeConverter. Controlla [qui] (http://msdn.microsoft.com/en-us/library/system.componentmodel.typeconverterattribute.aspx) e [qui] (http://msdn.microsoft.com/en-us/library/ ayybcxe5.aspx). –

+0

@mrnye - per informazioni, puoi scrivere i tuoi convertitori di tipi abbastanza facilmente - basta annotare i tuoi tipi personalizzati con '[TypeConverter (...)]' per dirgli quello che vuoi. È possibile * anche * cambiare i convertitori per i tipi esistenti, se lo si desidera ... –

3

Ecco una versione semplice:

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; 
} 

Si riconoscerà int e doppie, ma tutto il resto viene restituito come una stringa. Il problema con la gestione delle enumerazioni è che non c'è modo di sapere a quale enum appartiene un valore e non c'è modo di dire se dovrebbe essere una stringa o meno. Altri problemi sono che non gestisce date/orari o decimali (come vorresti li distinguono da doppie?), Ecc

Se siete disposti a modificare il codice in questo modo:

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

allora diventa sostanzialmente più 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: Aggiunto una parentesi mancante

+0

E comunque tale funzione (e come il tuo) restituirà 'object' in modo che OP esegua il cast di qualche tipo:' (int) ConvertToAny ("1") '. In altre parole (IMO) - questo non è possibile/l'attività non ha senso. Solo CLR, non C# può contenere funzioni diverse solo restituendo il tipo – abatishchev

+0

Vedere EDIT2 per ulteriori informazioni. Nel mio uso specifico, posso ottenere il tipo di classe di destinazione tramite la proprietà PropertyType. Ciò renderebbe semplice l'enumerazione delle enumerazioni. Permetterebbe inoltre di distinguere tra galleggianti/doppi/decimali/ecc. Tuttavia, speravo di non scrivere una grande dichiarazione if/case per ogni tipo di classe (anche se immagino che non sarebbe ** quella ** disordinata). – mike

+0

myrne: la risposta di Marc potrebbe essere ancora migliore, ma ho modificato la mia per supportare enum, costruttori che accettano una stringa e qualsiasi cosa abbia una funzione Parse. – Gabe

0

so che questo non risponde alla tua domanda, ma hai guardato Dapper micro ORM ?
È semplicissimo (rispetto a LINQ to SQL o, per tale motivo, Entity Framework) e fa ciò che vuoi.

considerare questo:

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 si è confezionato in un single file e, come riferito, is used by StackOverflow team (tranne LINQ to SQL).

3
public T Get<T>(string val) 
{ 
    if (!string.IsNullOrWhiteSpace(val)) 
     return (T) TypeDescriptor.GetConverter(typeof (T)).ConvertFromString(val); 
    else 
     return default(T); 
} 
+1

Sarebbe bello una spiegazione su cosa il codice fa, invece di incollare il codice – Joel

+0

string parse a qualsiasi generico – RouR

+2

Intendevo premere il pulsante [modifica] e aggiungere un po 'di contesto! :) – Joel