Getting the public static readonly strings and public const strings (and their values) from a class
Having members in stead of string literals scattered all over the place allows you to do compile timing checking, which I’m a big fan of as in the age of things getting more and more dynamic, you need to have as many sanity checks as possible.
One of the checks is to verify these const members and their values. Sometimes you need the list of members, sometimes the list of values, and sometimes each member value should be the same as the member name.
The listing below shows the code I came up with.
It works with code like this, and those can have more code and other fields (non string, non public) in them as well:
namespace bo.Literals
{
public class FooBarPublicStringConstants
{
public const string Foo = "Foo";
public const string Bar = "Bar";
}
public class FooBarPublicStaticReadOnlyStringFields
{
public static readonly string Foo = "Foo";
public static readonly string Bar = "Bar";
}
}
I started out with this code, but that is limited to classes only having public const fields. Not flexible enough.
There are a few tricks used.
The first trick is how to obtain the name of a method parameter without using a string literal (you now probably get it that I don’t like literals). Thanks to Rinat Abdullin, he provided the GetName method.
The reflection of public const strings and public static readonly strings is based on the FieldInfo that you get back from the Type.GetFields method. Both of them are public static fields in the current class. So you pass BindingFlags.Public (for public), BindingFlags.Static (for static) andBindingFlags.DeclaredOnly (for the current class). Since the BindingFlags enum has a FlagsAttribute, you can or those flags together.
Then you can set them apart:test for the FieldInfo.IsInitOnly property (public static readonly strings) or FieldInfo.IsLiteral property (public string constant).Or test the the FieldInfo.Attributes property (of type FieldAttributes; test for the enum flags InitOnly or Literal).
Finally you might want to need to check if the name of the field equals its content. This is what getMemberValueString does:For public const string fields, it gets the FieldInfo.GetRawConstantValue(), for public static readonly strings, it gets the FieldInfo.GetValue() passing null as instance (because they are static fields, not instance fields).
Knowing the details is not difficult, finding the details takes a bit of time, hence below is the source (that could use a tad bit more argument checking).
Note that if you are going to call methods like “GetName” with a non-reference type, you get an error like “The type must be a reference type in order to use it as parameter ‘T’ in the generic type or method“. That’s because of the “where T: class“. I think the GenericTypeCache should work without that generic constraint, but haven’t tried yet.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Linq.Expressions;
namespace bo.Reflection
{
public class Reflector
{
// http://abdullin.com/journal/2008/12/13/how-to-find-out-variable-or-parameter-name-in-c.html
public static string GetName<T>(T item) where T : class
{
return GenericTypeCache<T>.Name;
}
public static List<string> GetPublicStringConstants<T>() where T : class
{
IEnumerable<FieldInfo> fieldInfos = GetPublicStringConstantFieldInfos<T>();
List<string> result = getMemberNames(fieldInfos);
return result;
}
public static IEnumerable<FieldInfo> GetPublicStringConstantFieldInfos<T>() where T : class
{
var result = getPublicStaticDeclaredOnlyFieldInfos<T>(fieldInfo =>
(fieldInfo.IsLiteral && // literal constant
(fieldInfo.FieldType.FullName == typeof(string).FullName))
)
;
return result;
}
public static List<string> GetPublicStaticReadOnlyStringFields<T>() where T : class
{
IEnumerable<FieldInfo> fieldInfos = GetPublicStaticReadOnlyStringFieldFieldInfos<T>();
List<string> result = getMemberNames(fieldInfos);
return result;
}
public static IEnumerable<FieldInfo> GetPublicStaticReadOnlyStringFieldFieldInfos<T>() where T : class
{
var result = getPublicStaticDeclaredOnlyFieldInfos<T>(fieldInfo =>
(fieldInfo.IsInitOnly && // readonly field
(fieldInfo.FieldType.FullName == typeof(string).FullName))
)
;
return result;
}
public static bool NameMatchesValue(FieldInfo fieldInfo)
{
bool result = fieldInfo.Name == getMemberValueString(fieldInfo);
return result;
}
private static FieldInfo[] getPublicStaticDeclaredOnlyFieldInfos<T>() where T : class
{
FieldInfo[] publicStaticDeclaredOnlyFieldInfos = typeof(T).GetFields(
BindingFlags.Public // only public
| BindingFlags.Static // statics include consts
| BindingFlags.DeclaredOnly // only the T class
);
return publicStaticDeclaredOnlyFieldInfos;
}
private static IEnumerable<FieldInfo> getPublicStaticDeclaredOnlyFieldInfos<T>(Func<FieldInfo, bool> predicate) where T : class
{
FieldInfo[] publicStaticDeclaredOnlyFieldInfos = getPublicStaticDeclaredOnlyFieldInfos<T>();
IEnumerable<FieldInfo> result = publicStaticDeclaredOnlyFieldInfos.Where(predicate);
return result;
}
private static string getMemberValueString(FieldInfo fieldInfo)
{
string fieldInfoName = GetName(new { fieldInfo });
if (null == fieldInfo)
throw new ArgumentNullException(fieldInfoName);
if (!fieldInfo.IsStatic)
throw new ArgumentException(string.Format("{0} {1} must be static", fieldInfoName, fieldInfo.Name), fieldInfoName);
object member = (fieldInfo.IsLiteral) ?
fieldInfo.GetRawConstantValue()
:
fieldInfo.GetValue(null);
string result = member.ToString();
return result;
}
private static List<string> getMemberNames(IEnumerable<FieldInfo> fieldInfos)
{
List<string> result = new List<string>();
foreach (FieldInfo fieldInfo in fieldInfos)
{
result.Add(getMemberValueString(fieldInfo));
}
return result;
}
}
}
Reference: Getting the public static readonly strings and public const strings (and their values) from a class from our NCG partner Jeroen Pluimers at the The Wiert Corner blog.