开发者

Trim all string properties

I need to trim some string properties in my objects, but I don't want to go to all objects and properties and in the set properties do the trim method (there is a lot of objects, 300+ and a lot of string properties).

One开发者_如何学编程 tip: all my objects have a super class called CoreTransaction, so I can use it (with some kind of reflection) to do this thing more easily.

Is that possible?


var stringProperties = obj.GetType().GetProperties()
                          .Where(p => p.PropertyType == typeof (string));

foreach (var stringProperty in stringProperties)
{
    string currentValue = (string) stringProperty.GetValue(obj, null);
    stringProperty.SetValue(obj, currentValue.Trim(), null) ;
}


Thank you to Bala R for your solution to the OP's problem. I converted your solution to an extension method and fixed a problem where null values were throwing errors.

    /// <summary>Trim all String properties of the given object</summary>
    public static TSelf TrimStringProperties<TSelf>(this TSelf input)
    {
        var stringProperties = input.GetType().GetProperties()
            .Where(p => p.PropertyType == typeof(string) && p.CanWrite);

        foreach (var stringProperty in stringProperties)
        {
            string currentValue = (string)stringProperty.GetValue(input, null);
            if (currentValue != null)
                stringProperty.SetValue(input, currentValue.Trim(), null);
        }
        return input;
    }


I fixed landi's answer to accommodate child nullable objects and handle IEnumerable collections (loop through a List of object and trim string properties). I made an edit to his answer which was rejected for not being on topic, but that's a load of garbage. Hopefully this helps someone, as landi's answer didn't work on every object type I had. Now it does.

public static class ExtensionMethods
{
    public static void TrimAllStrings<TSelf>(this TSelf obj)
    {
        if(obj != null)
        {
            if(obj is IEnumerable)
            {
                foreach(var listItem in obj as IEnumerable)
                {
                    listItem.TrimAllStrings();
                }
            }
            else
            {
                BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;

                foreach (PropertyInfo p in obj.GetType().GetProperties(flags))
                {
                    Type currentNodeType = p.PropertyType;
                    if (currentNodeType == typeof (String))
                    {
                        string currentValue = (string)p.GetValue(obj, null);
                        if (currentValue != null)
                        {
                            p.SetValue(obj, currentValue.Trim(), null);
                        }
                    }
                    // see http://stackoverflow.com/questions/4444908/detecting-native-objects-with-reflection
                    else if (currentNodeType != typeof (object) && Type.GetTypeCode(currentNodeType) == TypeCode.Object)
                    {
                        p.GetValue(obj, null).TrimAllStrings();
                    }
                }
            }
        }
    }
}


I have written a Extension Method which also takes care of subclasses and strings on referenced classes (like parent.Child.Name)

public static class ExtensionMethods
{
    public static void TrimAllStrings<TSelf>(this TSelf obj)
    {
        BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;

        foreach (PropertyInfo p in obj.GetType().GetProperties(flags))
        {
            Type currentNodeType = p.PropertyType;
            if (currentNodeType == typeof (String))
            {
                string currentValue = (string)p.GetValue(obj, null);
                if (currentValue != null)
                {
                    p.SetValue(obj, currentValue.Trim(), null);
                }
            }
            // see http://stackoverflow.com/questions/4444908/detecting-native-objects-with-reflection
            else if (currentNodeType != typeof (object) && Type.GetTypeCode(currentNodeType) == TypeCode.Object)
            {
                p.GetValue(obj, null).TrimAllStrings();
            }
        }
    }
}


I'm not sure about changing the behaviour of your accessors. That doesn't sound easy at all. How about adding the trimming to your base class?

    class CoreTransaction
    {
        public void Trim()
        {
            IEnumerable<PropertyInfo> stringProperties =
                this.GetType().GetProperties()
                .Where(p => p.PropertyType == typeof(string) && p.CanRead && p.CanWrite);

            foreach (PropertyInfo property in stringProperties)
            {
                string value = (string)property.GetValue(this, null);
                value = value.Trim();
                property.SetValue(this, value, null);
            }
        }
    }

(Also, note the check that your fields can be both read and written to.)

 

EDIT: You could then add something like this to your base class, and trim all of them in one go. The WeakReference class will allow you to easily keep track of your instances without getting in the way of the garbage collector:

class CoreTransaction
{
    private static List<WeakReference> allCoreTransactions = new List<WeakReference>();

    public CoreTransaction()
    {
        allCoreTransactions.Add(new WeakReference(this));
    }

    public static void TrimAll()
    {
        foreach (WeakReference reference in allCoreTransactions)
        {
            if (reference.IsAlive)
            {
                ((CoreTransaction)reference.Target).Trim();
            }
        }
    }
}


You could use reflection to do something like this:

// o is your instance object 
List<PropertyInfo> fields = o.GetType().GetProperties()
        .Where(i => i.PropertyType == typeof(string));
fields.ForEach(i => i.SetValue(o, ((string)i.GetValue(o, null)).Trim(), new object[]{}));


Thank landi for his solution. I altered his method to add support for Classes with Index Parameters and also to check if the obj is null before continuing.

public static void TrimAllStrings<TSelf>(this TSelf obj)
    {
        if (obj == null)
            return;

        BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;

        foreach (PropertyInfo p in obj.GetType().GetProperties(flags))
        {
            Type currentNodeType = p.PropertyType;
            if (currentNodeType == typeof(String))
            {
                string currentValue = (string)p.GetValue(obj, null);
                if (currentValue != null)
                {
                    p.SetValue(obj, currentValue.Trim(), null);
                }
            }
            // see http://stackoverflow.com/questions/4444908/detecting-native-objects-with-reflection
            else if (currentNodeType != typeof(object) && Type.GetTypeCode(currentNodeType) == TypeCode.Object)
            {
                if (p.GetIndexParameters().Length == 0)
                {
                    p.GetValue(obj, null).TrimAllStrings();
                }else
                {
                    p.GetValue(obj, new Object[] { 0 }).TrimAllStrings();
                }
            }
        }
    }


Extended Own's solution and added a check that it's possible to write to property. Had some "Property set method not found" errors due to a Uri property.

public static class ExtensionMethods
{
    public static void TrimAllStrings<TSelf>(this TSelf obj)
    {
        if(obj != null)
        {
            if(obj is IEnumerable)
            {
                foreach(var listItem in obj as IEnumerable)
                {
                    listItem.TrimAllStrings();
                }
            }
            else
            {
                BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;

                foreach (PropertyInfo p in obj.GetType().GetProperties(flags))
                {
                    Type currentNodeType = p.PropertyType;
                    if (currentNodeType == typeof (String))
                    {
                        string currentValue = (string)p.GetValue(obj, null);
                        if (currentValue != null && p.CanWrite)
                        {
                            p.SetValue(obj, currentValue.Trim(), null);
                        }
                    }
                    // see http://stackoverflow.com/questions/4444908/detecting-native-objects-with-reflection
                    else if (currentNodeType != typeof (object) && Type.GetTypeCode(currentNodeType) == TypeCode.Object)
                    {
                        p.GetValue(obj, null).TrimAllStrings();
                    }
                }
            }
        }
    }
}


For those who use VB.NET, I converted thrawnis's answer and added a condition to only return those properties that are not ReadOnly. Otherwise if your class has readonly properties you will get runtime error when you try to SetValue for those properties.

''' <summary>
''' Trim all NOT ReadOnly String properties of the given object
''' </summary>
<Extension()>
Public Function TrimStringProperties(Of T)(ByVal input As T) As T
    Dim stringProperties = input.GetType().GetProperties().Where(Function(p) p.PropertyType = GetType(String) AndAlso p.CanWrite)

    For Each stringProperty In stringProperties
        Dim currentValue As String = Convert.ToString(stringProperty.GetValue(input, Nothing))
        If currentValue IsNot Nothing Then
            stringProperty.SetValue(input, currentValue.Trim(), Nothing)
        End If
    Next
    Return input
End Function


You can try it:

static public class Trim<T>
    where T : class
{
    static public readonly Action<T> TrimAllStringFields = Trim<T>.CreateTrimAllStringFields();

    static private Action<T> CreatTrimAllStringFields()
    {
        var instance = Expression.Parameter(typeof(T));
        return Expression.Lambda<Action<T>>(Expression.Block(instance.Type.GetFields(BindingsFlags.Instance| BindingFlags.NonPublic | BindingFlags.Public).Select(field => Expression.Assign(Expression.Field(instance, field)) as Expression), instance).Compile();
    }
}

Use it like this :

var myinstance = new MyClass();
Trim<MyClass>.TrimAllStringFields(myinstance);


I took OwN's answer but made these changes:

  • used early exit to decrease the nesting of if
  • used var everywhere, renaming a few variables
  • added Unit Tests

ObjectExtensions.cs

using System;
using System.Collections;
using System.Reflection;

namespace YourProject.Infrastructure.Extensions
{
    public static class ObjectExtensions
    {
        // Derived from https://stackoverflow.com/a/50193184/
        public static void TrimAllStrings<TSelf>(this TSelf obj)
        {
            if (obj == null)
            {
                return;
            }

            if (obj is IEnumerable)
            {
                foreach (var item in obj as IEnumerable)
                {
                    item.TrimAllStrings();
                }
                return;
            }

            var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;
            foreach (var prop in obj.GetType().GetProperties(flags))
            {
                var nodeType = prop.PropertyType;
                if (nodeType == typeof(String))
                {
                    string currentValue = (string)prop.GetValue(obj, null);
                    if (currentValue != null)
                    {
                        prop.SetValue(obj, currentValue.Trim(), null);
                    }
                }
                // see http://stackoverflow.com/questions/4444908/detecting-native-objects-with-reflection
                else if (nodeType != typeof(object) && Type.GetTypeCode(nodeType) == TypeCode.Object)
                {
                    prop.GetValue(obj, null).TrimAllStrings();
                }
            }
        }
    }
}

ObjectExtensionsTests.cs

using System.Collections.Generic;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using YourProject.Infrastructure.Extensions;

namespace YourProjectTests.Infrastructure.Extensions
{
    [TestClass]
    public class ObjectExtensionsTests
    {
        [TestMethod]
        public void NullObject_DoesNothing()
        {
            // Arrange
            SomeStringPropertiesClass test = null;

            // Act
            test.TrimAllStrings();
        }

        public class NoStringPropertiesClass
        {
            public int IntProperty { get; set; }
        }

        [TestMethod]
        public void NoStringProperties_DoesNothing()
        {
            // Arrange
            var test = new NoStringPropertiesClass()
            {
                IntProperty = 42
            };

            // Act
            test.TrimAllStrings();

            // Assert
            test.IntProperty.Should().Be(42);
        }

        public class SomeStringPropertiesClass
        {
            public int IntProperty { get; set; }
            public string StringProperty1 { get; set; }
            public string StringProperty2 { get; set; }
            public string StringProperty3 { get; set; }
            public List<SomeStringPropertiesClass> Children { get; set; } = new();
        }

        [TestMethod]
        public void SomeStringProperties_DoesTrimStrings()
        {
            // Arrange
            var test = new SomeStringPropertiesClass()
            {
                IntProperty = 42,
                StringProperty1 = "Already trimmed string",
                StringProperty2 = "  Needs trimming  ",
                StringProperty3 = "",
                Children = new()
                {
                    new SomeStringPropertiesClass()
                    {
                        StringProperty1 = "  Child that needs trimming  ",
                        StringProperty2 = null,
                        StringProperty3 = "  Child that needs trimming .  ",
                        Children = new()
                        {
                            null,
                            new SomeStringPropertiesClass()
                            {
                                StringProperty2 = "  Grandchild that needs trimming  ",
                            },
                            null
                        }
                    }
                }
            };

            // Act
            test.TrimAllStrings();

            // Assert
            test.IntProperty.Should().Be(42);
            test.StringProperty1.Should().Be("Already trimmed string");
            test.StringProperty2.Should().Be("Needs trimming");
            test.StringProperty3.Should().BeEmpty();
            test.Children[0].StringProperty1.Should().Be("Child that needs trimming");
            test.Children[0].StringProperty2.Should().BeNull();
            test.Children[0].StringProperty3.Should().Be("Child that needs trimming .");
            test.Children[0].Children[1].StringProperty1.Should().BeNull();
            test.Children[0].Children[1].StringProperty2.Should().Be("Grandchild that needs trimming");
            test.Children[0].Children[1].StringProperty3.Should().BeNull();
        }
    }
}


Thanks @Teter28 for the idea with code generation. It's much more effective than solutions with reflection. The provided code example does not work. Here's ready for use example.

public static class Trimmer<T>
{
    private static readonly Action<T> TrimAllStringFieldsAction = CreateTrimAllStringPropertiesMethod();

    public static void TrimAllStringProperties(T parameter)
    {
        TrimAllStringFieldsAction(parameter);
    }

    private static Action<T> CreateTrimAllStringPropertiesMethod()
    {
        var parameter = Expression.Parameter(typeof(T));
        var trimMethod = typeof(string).GetMethod(nameof(string.Trim), Type.EmptyTypes);
        return Expression.Lambda<Action<T>>(
            Expression.Block(
                parameter.Type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                    .Where(propertyInfo => propertyInfo.PropertyType == typeof(string))
                    .Select(propertyInfo => Expression.Assign(
                        Expression.Property(parameter, propertyInfo),
                        Expression.Call(Expression.Property(parameter, propertyInfo), trimMethod!)))),
            parameter)
            .Compile();
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜