2009-12-31 2 views
9

Dire che ho una classe con una proprietàtipizzazione forte un nome di proprietà in .NET

Public Class MyClass 
    Public Property MyItem() as Object 
     .... 
    End Property 
End Class 

devo passare il nome della proprietà ad una chiamata di funzione. (Si prega di non chiedere perché dovrebbe essere fatto in questo modo, è un quadro di terze parti). Ad esempio

SomeFunc("MyItem") 

Ma quello che vorrei fare è cambiare la stringa in un parametro fortemente tipizzato. Significa, se il nome della proprietà viene rinominato o modificato, dovrebbe riflettersi anche qui.

Quindi qualcosa di questo tipo:

Dim objectForStrongTyping as New MyClass() 
SomeFunc(objectForStrongTyping.MyItem().Name()) 

Sono sicuro che questo non funzionerà. C'è un modo in cui questa forte tipizzazione può essere fatta? (C# o VB.NET, qualsiasi cosa è cool)

risposta

20

Ecco una soluzione che utilizza le classi da System.Linq.Expressions.

static MemberInfo GetMemberInfo<TObject, TProperty>(
    Expression<Func<TObject, TProperty>> expression 
) { 
    var member = expression.Body as MemberExpression; 
    if (member != null) { 
     return member.Member; 
    } 

    throw new ArgumentException("expression"); 
} 

Basta lanciare questo in una classe da qualche parte (ExpressionHelper?).

Usage:

class SomeClass { 
    public string SomeProperty { get; set; } 
} 

MemberInfo member = GetMemberInfo((SomeClass s) => s.SomeProperty); 
Console.WriteLine(member.Name); // prints "SomeProperty" on the console 
+0

+1 per un ottimo attacco rapido! –

+0

Bello, può essere racchiuso in un'estensione? – ChaosPandion

0

Si può sempre utilizzare una classe statica che contiene string costanti invece di passare in un string letterale:

public static class ObjectForStrongTyping 
{ 
    public const string MyItem = "MyItem"; 
    public const string MyOtherItem = "MyOtherItem"; 
    // ... 
} 

Il tuo codice sarebbe poi diventato:

SomeFunc(ObjectForStrongTyping.MyItem); 
+0

+1: mentre non è un s "automatico" come LINQ e typeof (...) soluzioni, la sua super semplice e la manutenzione è minima. – Juliet

1

Se v'è solo una proprietà che si potrebbe fare questo - ottenere le informazioni di proprietà sulla prima proprietà della classe:

//C# syntax 
typeof(MyClass).GetProperties()[0].Name; 

'VB syntax 
GetType(MyClass).GetProperties()(0).Name 

EDIT Risulta, dove è possibile utilizzare le espressioni, è inoltre possibile utilizzare projection per questo tipo di riflessione (codice C#).

public static class ObjectExtensions { 
    public static string GetVariableName<T>(this T obj) { 
     System.Reflection.PropertyInfo[] objGetTypeGetProperties = obj.GetType().GetProperties(); 

     if(objGetTypeGetProperties.Length == 1) 
      return objGetTypeGetProperties[0].Name; 
     else 
      throw new ArgumentException("object must contain one property"); 
    } 
} 

class Program { 
    static void Main(string[] args) { 
     Console.WriteLine(Console.WriteLine(new { (new MyClass()).MyItem}.GetVariableName());); 
    } 
} 

Con questa soluzione, la classe può avere un numero qualsiasi di proprietà, è possibile ottenere qualsiasi altro nome.

+0

Ouch. Ora invece di preoccuparci delle proprietà che cambiano i nomi, dobbiamo preoccuparci di aggiungere/riordinare le proprietà? – Greg

+0

Sì, è piuttosto sporco, ma come per l'OP, la classe ha solo una proprietà. Vedi modifica per una soluzione migliore. –

+0

Con EDIT, questa è una risposta molto interessante. Il metodo di proiezione è bello. C'è una raccomandazione per la quale è più logico usare? In VB.Net la sintassi del tipo anonimo è più concisa rispetto alla sintassi lambda. – mrmillsy

3

Questa soluzione funziona sia in C# e VB.NET, ma la sintassi VB.NET per le funzioni lambda non è così pulito, che farebbero probabilmente questa soluzione meno attraente in VB. I miei esempi saranno in C#.

È possibile ottenere l'effetto che si desidera utilizzare le funzioni di funzione lambda e albero di espressione di C# 3. In sostanza, si potrebbe scrivere una funzione wrapper chiamato SomeFuncHelper e chiamare in questo modo:

MyClass objForStrongTyping = new MyClass(); 
SomeFuncHelper(() => objForStrongTyping.MyItem); 

SomeFuncHelper è implementato come segue:

void SomeFuncHelper(Expression<Func<object>> expression) 
{ 
    string propertyName = /* get name by examining expression */; 
    SomeFunc(propertyName); 
} 

L'espressione lambda () => objForStrongTyping.MyItem si traduce in un oggetto espressione che è passato SomeFuncHelper. SomeFuncHelper esamina Expression, estrae il nome della proprietà e chiama SomeFunc.Nel mio test rapido, il seguente codice funziona per recuperare il nome della proprietà, assumendo SomeFuncHelper è sempre chiamato come sopra (cioè () => someObject.SomeProperty):

propertyName = ((MemberExpression) ((UnaryExpression) expression.Body).Operand).Member.Name; 

probabilmente si vorrà di leggere su alberi di espressione e lavorare con il codice per renderlo più robusto, ma questa è l'idea generale.

Aggiornamento: Questo è simile alla soluzione di Jason, ma permette l'espressione lambda all'interno della chiamata di aiuto-funzione per essere un po 'più semplice (() => obj.Property invece di (SomeType obj) => obj.Property). Ovviamente, questo è più semplice se hai già un'istanza del tipo seduta in giro.

0

La soluzione migliore penso sia generare costanti statiche usando T4 (ad esempio T4MVC).

public static class StaticSampleClass 
{ 
    public const string MyProperty = "MyProperty"; 
} 

Credetemi quando hai un sacco di chiamate di riflessione e di espressione LINQ è smontare le prestazioni della vostra applicazione.

La cosa brutta è che T4 non c'è più nel net core. :(

Buona cosa in C# 6.0 u possibile utilizzare nameof(SampleClass.MyProperty)

Nel peggiore dei casi u possibile utilizzare il seguente esempio:

using System.Linq.Expressions; 

namespace ConsoleApp1 
{ 
    public static class Helper 
    { 
     public static string GetPropertyName<T>(Expression<Func<T, object>> propertyExpression) 
     { 
      var member = propertyExpression.Body as MemberExpression; 
      if (member != null) 
       return member.Member.Name; 
      else 
       throw new ArgumentNullException("Property name not found."); 
     } 
     public static string GetPropertyName<T>(this T obj, Expression<Func<T, object>> propertyExpression) 
     { 
      return GetPropertyName(propertyExpression); 
     } 
    } 

    public class SampleClass 
    { 
     public string MyProperty { get; set; } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      // Property name of type 
      Console.WriteLine(Helper.GetPropertyName<SampleClass>(x => x.MyProperty)); 

      // Property name of instance 
      var someObject = new SampleClass(); 
      Console.WriteLine(someObject.GetPropertyName(x => x.MyProperty)); 

      Console.ReadKey(); 
     } 
    } 
} 

risultati di performance (1 milione di volte chiamano):

StaticSampleClass.MyProperty-8 ms

nameof(SampleClass.MyProperty) - 8 ms

Helper.GetPropertyName<SampleClass>(x => x.MyProperty) - 2000 ms

3

In C# 6.0 C'è una nuova funzione denominata nameof. Fondamentalmente si può fare questo:

var name = nameof(MyClass.MyItem); 

Guardando convertitore di codice Telerik da C# a VB sembra che questo è il VB equivalente:

Dim name = nameof([MyClass].MyItem) 

in modo da poter effettuare le seguenti operazioni:

SomeFunc(nameof(MyClass.MyItem)); 

Ecco il riferimento alla documentazione microsoft: https://docs.microsoft.com/en-us/dotnet/articles/csharp/language-reference/keywords/nameof