views:

4935

answers:

12

The class below contains the field city.

I need to dynamically determine the field's name as it is typed in the class declaration i.e. I need to get the string "city" from an instance of the object city.

I have tried to do this by examining its Type in DoSomething() but can't find it when examining the contents of the Type in the debugger.

Is it possible?

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

Some people in their answers below have asked me why I want to do this. Here's why.

In my real world situation, there is a custom attribute above city.

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

I need this attribute in other code. To get the attribute, I use reflection. And in the reflection code I need to type the string "city"

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
  }

}

Now this isn't type safe. If I changed the variable "city" to "workCity" in my field declaration in Person this line would fail unless I knew to update the string

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

So I am trying to find some way to pass the string to this code without physically typing it.

Yes, I could declare it as a string constant in Person (or something like that) but that would still be typing it twice.

Phew! That was tough to explain!!

Thanks

Thanks to all who answered this * a lot*. It sent me on a new path to better understand lambda expressions. And it created a new question.

+4  A: 

city in this case is an instance of type string. When you call .GetType() you return the actual string type, which has no knowledge at all of your particular city instance.

I'm having a hard time seeing why you can't just type "city" in the code as a string literal here, if that's what you need. Perhaps it would help if you shared what you want to use this value for and in what circumstances you will call your DoSomething() function.

At the moment, my best guess is that what you really want to do is reflect the entire Person class to get a list of the fields in that class:

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


Okay, based on your edit I have some more for you.

You can find the name of fields that are decorated with your attribute at run-time like this:

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
}

You can also use binding flags in the first GetMembers() call to limit it to just fields, if you want.

Joel Coehoorn
I could do that but it isn't type safe.If someone changed the field's name to LocalCity eg:public string LocalCity = "New York";My code could crash unless every instance of "city" was replaced by "LocalCity"
Petras
Unless you are dealing with serialization or debugging/logging, you really shouldn't do it this way.
Samuel
@Petras: if someone did that you'd have to recompile anyway. So how are you using this string?
Joel Coehoorn
@Samuel: maybe that's what he's doing. i don't see why else he'd need the value as a string.
Joel Coehoorn
@Joel: He might, but I think he isn't. The way he worded it seems to suggest other intentions.
Samuel
I have added a detailed explanation as to why I want this in the original question
Petras
Sorry, my stupid typo, I have changed the line to Type t = typeof(Person);
Petras
No prob: check the code now. (as a side note: I hate having to format complex lambdas :( )
Joel Coehoorn
A: 
t.GetField("city", BindingFlags.Public | BindingFlags.Instance);

or you can call GetFields() to get all fields

Canton
t == typeof(string) so getting its fields is pretty much useless.
Samuel
oops sorry. I thought t = typeof(Person)
Canton
A: 

You need to call get type on the class Person. The iterate the fields of the class as in the answer below

Preet Sangha
A: 

This is not possible (I think it actually is but involes several hacks and using lambdas). If you want to store attributes about a Person and be able to get the name of the attribute easily, I suggest using a Dictionary<TKey, TValue> from the System.Collections.Generic namespace.

And you can always make public properties that wrap the dictionary.

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

And you can get a list of all attributes with attributes.Keys.

Samuel
A: 

Have a look at this post as it looks similar to what you're trying to do:

http://stackoverflow.com/questions/72121/finding-the-variable-name-passed-to-a-function-in-c

(especially Konrad Rudolph's answer) Another approach could be to just add "city" as one of the parameters in the attribute and fish that out later.

pbz
A: 

You are already looping through the collection of FieldInfo objects. Look for your attribute on those and when you find the FieldInfo that contains your attribute, you have the one you want. Then call .Name on it.

system.reflection.fieldinfo.attributes

brendanjerwin
+2  A: 

You mentioned "i.e. I need to get the string "city" from an instance of the object city." Are you looking to get the field name from the value of the field. For example:If there are 2 Person object one with city "New York" and the other with city "London", are you looking for the function to return "city". Is this what you mean by dynamic?


With your current design you will always need to compare the name of the field from the FieldInfo against a string. What if you instead decouple this so that you hold the identifier to use for comparison purposes during reflection as part of the attribute. Something like this:

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

}
 }

You can now freely rename _city field of Person to something else and your calling code will still work since the code using reflection is trying to identify the field using the ReflectionFields enum value set as part of initialization of the attribute set on the field.

Abhijeet Patel
You got it, if there were two objects then "city" would be returned by both
Petras
Based on your comment, I've edited my answer with a possible solution that might work for you. Hope this helps
Abhijeet Patel
+1  A: 

Two things here.

Number one, as someone above pointed out, you're getting the Type for string, not for Person. So typeof(Person).GetMembers() will get you the list of members.

Number two, and more importantly, it looks like you're misunderstanding the purpose of attributes. In general attributes are used to mark a member for specific processing or to add additional information. Here you're using the name to indicate what processing you want, and the attribute to specify parameters, which is mixing of metaphors, or something.

Abhijeet's answer is more appropriate, you mark the field as a city field, then do what you like with it. Where I disagree is that I would use different attribute classes, rather than an enumeration.

Something like:

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

This would allow you to use different parameters for MyCity vs MyName that would make more sense in the context of processing each.

I think with your 'yuk' comment above, you hit the nail on the head. That you would have to change a string constant if you rename your variable is an indicator that you're doing something wrong.

Darren Clark
@Darren:That would work as well, I was trying to limit the number of attribute classes that would have to be created,hence the enum approach,but if the problem at hand needed holding specifc state inside the attribute, I'd choose to create separate attribute classes. Just a personal choice.
Abhijeet Patel
Absolutely, didn't mean to imply what you suggested was "wrong", just not how I would do it. :)
Darren Clark
+1  A: 

May be, you need this. Works fine. I found this in http://www.dreamincode.net/forums/showtopic53253.htm


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

static void Check(Expression> 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));
}

// Output will be:
// Name is: 'domain'
// Value is: 'matrix'
Luciano
A: 

Abhijeet Patel, I get the following error message: "Attribute 'CustomFieldAttr' is not valid on this declaration type. It is only valid on 'field' declarations." Any ideas how to fix this?

Madmaximus
A: 

Yes its possible !!!

Try this out...

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

I know this is old question, but I was trying to achieve the same and google sent me here. After many hours I finally found a way. I hope somebody else will find this useful.

There are actually more ways to accomplish this:

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

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

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

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

The first is fastest. The last 2 are approx 20 times slower than the 1st one.

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

Markos