C#: How to get all public (both get and set) string properties of a type

I am trying to make a method that will go through a list of generic objects and replace all their properties of type string which is either null or empty with a replacement.

How is a good way to do this?

I have this kind of... shell... so far:

public static void ReplaceEmptyStrings<T>(List<T> list, string replacement)
{
    var properties = typeof(T).GetProperties( -- What BindingFlags? -- );

    foreach(var p in properties)
    {
        foreach(var item in list)
        {
            if(string.IsNullOrEmpty((string) p.GetValue(item, null)))
                p.SetValue(item, replacement, null);
        }
    }
}

So, how do I find all the properties of a type that are:

  • Of type string

  • Has public get

  • Has public set

    ?


I made this test class:

class TestSubject
{
    public string Public;
    private string Private;

    public string PublicPublic { get; set; }
    public string PublicPrivate { get; private set; }
    public string PrivatePublic { private get; set; }
    private string PrivatePrivate { get; set; }
}

The following does not work:

var properties = typeof(TestSubject)
        .GetProperties(BindingFlags.Instance|BindingFlags.Public)
        .Where(ø => ø.CanRead && ø.CanWrite)
        .Where(ø => ø.PropertyType == typeof(string));

If I print out the Name of those properties I get there, I get:

PublicPublic PublicPrivate PrivatePublic

In other words, I get two properties too much.


Note: This could probably be done in a better way... using nested foreach and reflection and all here... but if you have any great alternative ideas, please let me know cause I want to learn!


Solution 1:

Your code rewritten. Does not use LINQ nor var.

public static void ReplaceEmptyStrings<T>(List<T> list, string replacement)
{
    PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);

    foreach (PropertyInfo p in properties)
    {
        // Only work with strings
        if (p.PropertyType != typeof(string)) { continue; }

        // If not writable then cannot null it; if not readable then cannot check it's value
        if (!p.CanWrite || !p.CanRead) { continue; }

        MethodInfo mget = p.GetGetMethod(false);
        MethodInfo mset = p.GetSetMethod(false);

        // Get and set methods have to be public
        if (mget == null) { continue; }
        if (mset == null) { continue; }

        foreach (T item in list)
        {
            if (string.IsNullOrEmpty((string)p.GetValue(item, null)))
            {
                p.SetValue(item, replacement, null);
            }
        }
    }
}

Solution 2:

You will find the properties as such with BindingFlags.Public | BindingFlags.Instance. Then you will need to examine each PropertyInfo instance by checking the CanWrite and CanRead properties, in order to find out whether they are are readable and/or writeable.

Update: code example

PropertyInfo[] props = yourClassInstance.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
for (int i = 0; i < props.Length; i++)
{
    if (props[i].PropertyType == typeof(string) && props[i].CanWrite)
    {
        // do your update
    }
}

I looked into it more in detail after your update. If you also examine the MethodInfo objects returned by GetGetMethod and GetSetMethod you will hit the target, I think;

 var properties = typeof(TestSubject).GetProperties(BindingFlags.Instance | BindingFlags.Public)
        .Where(ø => ø.CanRead && ø.CanWrite)
        .Where(ø => ø.PropertyType == typeof(string))
        .Where(ø => ø.GetGetMethod(true).IsPublic)
        .Where(ø => ø.GetSetMethod(true).IsPublic);

By default these two methods return only public getters and setters (risking a NullReferenceException in a case like this), but passing true as above makes them also return private ones. Then you can examine the IsPublic (or IsPrivate) properties.