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

C#Reflection

C# Problem Overview


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!

C# Solutions


Solution 1 - C#

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 - C#

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.

Solution 3 - C#

If you don't specify any binding flags you will get the public, instance properties -- which is what you want. But then you will need to check if the PropertyType on the PropertyInfo object is of type String. Unless you know in advance, you'll also need to check whether the property is readable/writable as @Fredrik indicates.

using System.Linq;

public static void ReplaceEmptyStrings<T>(List<T> list, string replacement)
{
    var properties = typeof(T).GetProperties()
                              .Where( p => p.PropertyType == typeof(string) );
    foreach(var p in properties)
    {
        foreach(var item in list)
        {
            if(string.IsNullOrEmpty((string) p.GetValue(item, null)))
                p.SetValue(item, replacement, null);
        }
    }
}

Solution 4 - C#

BindingFlags.Public | BindingFlags.Instance should do it

GetSetMethod()

Solution 5 - C#

I suggest a different approach: AOP.
You can intercept the setter and set the desired value to a valid one. With PostSharp it's quite easy.

Solution 6 - C#

I agree with other answers, but I prefer to refactor the search itself to be easly queried with Linq, so the query could be as follow:

        var asm = Assembly.GetExecutingAssembly();
        var properties = (from prop
                              in asm.GetType()
                                .GetProperties(BindingFlags.Public | BindingFlags.Instance)
                          where 
                            prop.PropertyType == typeof (string) && 
                            prop.CanWrite && 
                            prop.CanRead
                          select prop).ToList();
        properties.ForEach(p => Debug.WriteLine(p.Name));

I took for my example the Assembly type, which hasn't read/write string properties, but if the same code search for just read properties, the result will be:

  • CodeBase
  • EscapedCodeBase
  • FullName
  • Location
  • ImageRuntimeVersion

Which are the string read-only Assembly type properties

Solution 7 - C#

var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);

foreach (var prop in properties)
{
    if (prop.PropertyType == typeof(string))
    {
        var value = prop.GetValue(t);
        if (value != null /* &&  add your condition if exists */)
        {
            return /* whatever you want in your method*/;
        }
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSvishView Question on Stackoverflow
Solution 1 - C#Colin BurnettView Answer on Stackoverflow
Solution 2 - C#Fredrik MörkView Answer on Stackoverflow
Solution 3 - C#tvanfossonView Answer on Stackoverflow
Solution 4 - C#ba__friendView Answer on Stackoverflow
Solution 5 - C#Ron KleinView Answer on Stackoverflow
Solution 6 - C#HoghweedView Answer on Stackoverflow
Solution 7 - C#BerkayView Answer on Stackoverflow