2009-04-04 13 views
33

Powiel możliwe:
Finding the Variable Name passed to a Function in C#Jak uzyskać nazwę zmiennej, która została fizycznie wpisana w deklaracji?

Poniższa klasa zawiera Citi Field.

Potrzebuję dynamicznie określać nazwę pola, ponieważ jest wpisana w deklaracji klasy , tj. Muszę uzyskać ciąg "miasto" z instancji obiektu miasta.

Próbowałem to zrobić, sprawdzając jego typ DoSomething(), ale nie można go znaleźć podczas sprawdzania zawartości typu w debugerze.

Czy to możliwe?

public class Person 
{ 
    public string city = "New York"; 

    public Person() 
    { 
    } 


    public void DoSomething() 
    { 
    Type t = city.GetType(); 

    string field_name = t.SomeUnkownFunction(); 
    //would return the string "city" if it existed! 
    } 
} 

Niektórzy ludzie w swoich odpowiedziach poniżej pytali mnie, dlaczego chcę to zrobić. Oto dlaczego.

W mojej prawdziwej sytuacji na świecie istnieje niestandardowy atrybut nad miastem.

[MyCustomAttribute("param1", "param2", etc)] 
public string city = "New York"; 

Potrzebuję tego atrybutu w innym kodzie. Aby uzyskać atrybut, używam odbicia. I w kodzie refleksji muszę wpisać ciąg „miasto”

MyCustomAttribute attr; 
Type t = typeof(Person); 

foreach (FieldInfo field in t.GetFields()) 
{ 

    if (field.Name == "city") 
    { 
    //do stuff when we find the field that has the attribute we need 
    } 

} 

Teraz to nie jest typ bezpieczne. Gdybym zmienił zmiennej „miasto” na „workCity” w moim oświadczeniu pola osobiście linia ta nie powiedzie się, chyba że wiedział zaktualizować ciąg

if (field.Name == "workCity") 
//I have to make this change in another file for this to still work, yuk! 
{ 
} 

Więc staram się znaleźć jakiś sposób, aby przekazać ciąg do ten kod bez fizycznego wpisywania go.

Tak, mógłbym zadeklarować to jako stałą łańcuchową w Person (lub coś w tym stylu), ale to nadal byłoby wpisywanie jej dwukrotnie.

Uff! Trudno to wyjaśnić!

Dzięki

Dziękujemy wszystkim, którzy odpowiedzieli na to * dużo *. To wysłało mnie na nową ścieżkę, aby lepiej zrozumieć wyrażenia lambda. I stworzył nowe pytanie.

+0

wysiąść, mam edytowane moją odpowiedź, a teraz niestety muszę iść do łóżka, a więc jesteś na własną rękę, stąd na zewnątrz. –

+1

W C# 6.0 i nowszych wersjach można użyć funkcji 'nameof', aby uzyskać numer , np." Nameof (miasto) "zwróci" miasto " –

Odpowiedz

7

city w tym przypadku jest to instancja o numerze typu string. Po wywołaniu .GetType() zwraca się rzeczywisty typ łańcucha, który ma brak wiedzy o swojej konkretnej instancji miejskiej.

Trudno mi zrozumieć, dlaczego nie można po prostu wpisać "miasto" w kodzie jako literał łańcuchowy tutaj, jeśli tego właśnie potrzebujesz. Być może pomógłbyś, gdybyś podzielił się tym, do czego chcesz wykorzystać tę wartość iw jakich okolicznościach nazwiesz swoją funkcję DoSomething().

Obecnie mój najlepszy Domyślam się, że to, co naprawdę chcesz robić to odzwierciedlać całą Person klasę, aby uzyskać listę pól w tej klasie:

public void DoSomething() 
{ 
    MemberInfo[] members = this.GetType().GetMembers(); 

    // now you can do whatever you want with each of the members, 
    // including checking their .Name properties. 
} 

porządku, opartego na Twoja edycja Mam dla ciebie więcej.

można znaleźć nazwy pól, które są ozdobione atrybutu w czasie wykonywania tak:

Type t = typeof(Person); 
foreach (MemberInfo member in t.GetMembers() 
      .Where(m => 
       m.GetCustomAttributes(typeof(MyCustomAttribute)).Any() )) 
{ 
    // "member" is a MemberInfo object for a Peson member that is 
    // decorated with your attribute 
} 

Można również użyć wiążących flagi w pierwszych GetMembers() zadzwonić, żeby go po prostu pola ograniczyć , Jeśli chcesz.

+0

Mogłem to zrobić, ale nie jest to bezpieczne. Jeśli ktoś zmienił nazwę pola na LocalCity, np .: public string LocalCity = "New York"; Mój kod mógł ulec awarii, chyba że każde wystąpienie "miasta" zostało zastąpione przez "LocalCity" – Petras

+0

Jeśli nie masz do czynienia z serializacją lub debugowaniem/rejestrowaniem, naprawdę nie powinieneś tego robić w ten sposób. – Samuel

+1

@Petras: jeśli ktoś to zrobił, to i tak musiałbyś dokonać ponownej kompilacji. Więc w jaki sposób używasz tego ciągu? –

0
t.GetField("city", BindingFlags.Public | BindingFlags.Instance); 

lub możesz zadzwonić GetFields(), aby uzyskać wszystkie pola

+0

t == typeof (string), więc zdobywanie jego pól jest praktycznie bezużyteczne – Samuel

+0

oops sorry Myślałem, że t = typeof (Person) – Canton

0

Trzeba zadzwonić dostać typ na klasy Osoba. Iteracja pól klasy tak, jak w odpowiedzi poniżej:

0

To nie jest możliwe (myślę, że tak naprawdę to tylko inwokuje kilka hacków i używa lambdas). Jeśli chcesz przechowywać atrybuty dotyczące Person i łatwo uzyskać nazwę atrybutu, sugeruję użycie Dictionary<TKey, TValue> z przestrzeni nazw System.Collections.Generic.

Zawsze można tworzyć publiczne właściwości, które zawijają słownik.

public class Person 
{ 
    Dictionary<string, string> attributes = new Dictionary<string, string(); 
    public string City 
    { 
    get { return attributes["city"]; } 
    set { attributes["city"] = value; } 
    } 

    public Person() 
    { 
    City = "New York"; 
    } 
} 

I można uzyskać listę wszystkich atrybutów z attributes.Keys.

0

Wystarczy popatrzeć na to stanowisko, jak to wygląda podobnie do tego, co starasz się zrobić:

Finding the variable name passed to a function

(zwłaszcza odpowiedź Konrad Rudolfa) Innym rozwiązaniem mogłoby być po prostu dodać „miasto” jako jeden parametrów w atrybucie i łowić ryby później.

4

Wspomniałeś „to znaczy, że trzeba uzyskać ciąg«miasto»z instancji obiektu miasta.” Czy chcesz uzyskać nazwę pola od wartości pola. Na przykład: Jeśli obiekt 2 osoba ma jeden z miastem "Nowy Jork", a drugi z miastem "Londyn", czy szukasz funkcji zwracania "miasta". Czy to masz na myśli dynamiczny?


Przy aktualnym projekcie zawsze będziesz musiał porównać nazwę pola z FieldInfo z ciągiem znaków. Co, jeśli zamiast tego rozdzielisz to, aby zachować identyfikator, który będzie używany do celów porównawczych podczas odbicia jako część atrybutu. coś takiego:

public enum ReflectionFields 
{ 
    CITY = 0, 
    STATE, 
    ZIP,  
    COUNTRY 

} 

[AttributeUsage(AttributeTargets.Field,AllowMultiple=false)] 
public class CustomFieldAttr : Attribute 
{ 
    public ReflectionFields Field { get; private set; } 
    public string MiscInfo { get; private set; } 

    public CustomFieldAttr(ReflectionFields field, string miscInfo) 
    { 
     Field = field; 
     MiscInfo = miscInfo; 
    } 
} 

public class Person 
{ 
    [CustomFieldAttr(ReflectionFields.CITY, "This is the primary city")] 
    public string _city = "New York"; 

    public Person() 
    { 
    } 
    public Person(string city) 
    { 
     _city = city; 
    } 

} 

public static class AttributeReader<T> where T:class 
{ 
    public static void Read(T t) 
    { 
     //get all fields which have the "CustomFieldAttribute applied to it" 
     var fields = t.GetType().GetFields().Where(f => f.GetCustomAttributes(typeof(CustomFieldAttr), true).Length == 1); 

     foreach (var field in fields) 
     { 
      var attr = field.GetCustomAttributes(typeof(CustomFieldAttr), true).First() as CustomFieldAttr; 
      if (attr.Field == ReflectionFields.CITY) 
      { 
       //You have the field and you know its the City,do whatever processing you need. 
       Console.WriteLine(field.Name); 
      } 
     }    
    } 
} 

public class Program 
{ 
    public static void Main(string[] args) 
    { 
     PPerson p1 = new PPerson("NewYork"); 
     PPerson p2 = new PPerson("London"); 
     AttributeReader<PPerson>.Read(p1); 
     AttributeReader<PPerson>.Read(p2); 

} 
} 

Teraz można dowolnie zmienić nazwę pola _City osoby, która czegoś innego i kod wywołanie będzie nadal działać, ponieważ kod przy użyciu odbicia próbuje zidentyfikować pole wykorzystując wartość enum ReflectionFields ustawiony jako część inicjalizacji zestawu atrybutów na polu.

+0

Masz to, gdyby były dwa obiekty, to "miasto" byłoby zwrócone zarówno przez – Petras

+0

Na podstawie twojego komentarza, zredagowałem swoją odpowiedź z możliwym rozwiązaniem, które może ci pomóc. Nadzieję, że to pomoże –

1

Dwie rzeczy tutaj.

Po pierwsze, jak wskazano powyżej, dostajesz typ dla napisu, a nie dla osoby. Tak więc typeof (Person) .GetMembers() dostanie listę członków.

Numer dwa, a co ważniejsze, wygląda na to, że nie rozumiesz celu atrybutów. Ogólnie atrybuty są używane do oznaczenia elementu do określonego przetwarzania lub do dodania dodatkowych informacji. Tutaj używasz nazwy, aby wskazać, jakie przetwarzanie chcesz i atrybut do określenia parametrów, które miesza metafory lub coś.

Odpowiedź Abhijeeta jest bardziej odpowiednia, należy oznaczyć pole jako a pole miejskie, a następnie zrobić z nim to, co lubisz. Tam, gdzie nie zgadzam się, używam różnych klas atrybutów zamiast wyliczania.

Coś jak:

public class MyAttribute : Attribute 
    { 

    } 

    [AttributeUsage(AttributeTargets.Field)] 
    public class MyCityAttribute : MyAttribute 
    { 
    } 

    [AttributeUsage(AttributeTargets.Field] 
    public class MyNameAttribute: MyAttribute 
    { 
    } 

    public class Person 
    { 

     [MyCity] 
     public string city = "New York"; 

     [MyCity] 
     public string workCity = "Chicago"; 

     [MyName] 
     public string fullName = "John Doe"; 

     public Person() 
     { 
     } 


     public void DoSomething() 
     { 
      Type t = typeof(Person); 
      FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.Public); 

      foreach (var field in fields) 
      { 
       MyAttribute[] attributes = field.GetCustomAttributes(typeof(MyAttribute)); 
       if (attributes.Count > 0) 
       { 
        if (attributes[0] is MyCityAttribute) 
        { 
         //Dosomething for city 
         break; 
        } 

        if (attributes[0] is MyNameAttribute) 
        { 
         //Dosomething for names 
         break; 
        } 
       } 
      } 
     } 
    } 

To pozwala stosować różne parametry dla Mycity vs MyName że więcej sensu w kontekście przetwarzania każdy.

Myślę, że z powyższym komentarzem "yuk" uderzasz w głowę. To, że musisz zmienić stałą łańcuchową, jeśli zmienisz nazwę zmiennej, jest wskaźnikiem, że robisz coś źle.

+0

@Darren: To też by działało, ja Próbowałem ograniczyć liczbę klas atrybutów, które musiałyby zostać stworzone, stąd podejście enum, ale jeśli problem wymagałby posiadania określonego stanu wewnątrz atrybutu, wybrałbym tworzenie oddzielnych klas atrybutów, po prostu osobisty wybór. –

+0

Absolutnie, nie oznacza to sugerowania, co sugerowałeś "źle", po prostu nie jak ja bym to robił :) –

49

Może potrzebujesz tego. Działa w porządku.

Znalazłem to here.

static void Main(string[] args) 
{ 
    var domain = "matrix"; 
    Check(() => domain); 
    Console.ReadLine(); 
} 

static void Check<T>(Expression<Func<T>> expr) 
{ 
    var body = ((MemberExpression)expr.Body); 
    Console.WriteLine("Name is: {0}", body.Member.Name); 
    Console.WriteLine("Value is: {0}", ((FieldInfo)body.Member) 
    .GetValue(((ConstantExpression)body.Expression).Value)); 
} 

wyjściowe będą:

 
Name is: 'domain' 
Value is: 'matrix' 
+1

Podoba mi się to rozwiązanie i zbudowałem zabawkę ction, który jest podobny do tego - ale czasami body.Member nie jest FieldInfo - jest to PropertyInfo - jak mogę uzyskać wartość w tym przypadku? – William

+1

Ghetto one-liner: Console.WriteLine (((MemberExpression) ((Wyrażenie >) (() = miasto)). Body) .Member.Name); –

+0

"Nie można rzucić obiektu typu" System.Linq.Expressions.TypedConstantExpression ", aby wpisać" System.Linq.Expressions.MemberExpression "." :( – weberc2

4

Tak, to możliwe !!!

Spróbuj tego ...

public string DoSomething(object city) 
    { 
     return city.GetType().GetProperty("Name",typeof(string)).GetValue(city,null); 
    } 
+0

Najlepszym sposobem na odzwierciedlenie nazwy zmiennej w większości przypadków –

38

Wiem, że to stare pytanie, ale starałem się osiągnąć to samo i google wysłał mnie tutaj. Po wielu godzinach w końcu znalazłem sposób. Mam nadzieję, że ktoś inny uzna to za przydatne.

Istnieje rzeczywiście więcej sposobów, aby to osiągnąć:

static void Main(string[] args) 
{ 
    GetName(new { var1 }); 
    GetName2(() => var1); 
    GetName3(() => var1); 
} 

static string GetName<T>(T item) where T : class 
{ 
    return typeof(T).GetProperties()[0].Name; 
} 

static string GetName2<T>(Expression<Func<T>> expr) 
{ 
    return ((MemberExpression)expr.Body).Member.Name; 
} 

static string GetName3<T>(Func<T> expr) 
{ 
    return expr.Target.GetType().Module.ResolveField(BitConverter.ToInt32(expr.Method.GetMethodBody().GetILAsByteArray(), 2)).Name; 
} 

Pierwszym z nich jest najszybszy. Ostatnie 2 są około 20 razy wolniejsze od pierwszego.

http://abdullin.com/journal/2008/12/13/how-to-find-out-variable-or-parameter-name-in-c.html

+2

Wielkie dzięki, Markos, twoja odpowiedź naprawdę pomógł – IneedHelp

+0

Rekwizytów dla rozwiązania "20 razy szybciej" – w00ngy

+0

Przyjemne alternatywy, przetestowałem to w aplikacji konsolowej.Kiedy zaczynając od rozwiązania Debug # 1 jest 5-10 razy szybciej niż # 2 i 1,5-2 razy szybciej niż # 3. Co ciekawe, gdy zaczynamy bez debugowania, to # 1 i # 2 wykonują prawie jednakowo, a zwycięzca jest tutaj nr 3, który jest około 2 razy szybszy niż # 1 i # 2. Wygląda na to, że moje rozwiązanie (i5 quad core) działa inaczej. Byłoby interesujące usłyszeć wyniki od innych użytkowników. – donttellya

Powiązane problemy