修剪所有字符串属性

本文关键字:属性 字符串 修剪 | 更新日期: 2023-09-27 18:11:09

我需要在我的对象中修剪一些字符串属性,但我不想去所有的对象和属性,并在set属性中做修剪方法(有很多对象,300+和很多字符串属性)。

提示:我所有的对象都有一个名为CoreTransaction的超类,所以我可以使用它(带有某种反射)来更容易地完成这件事。

这可能吗?

修剪所有字符串属性

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

感谢Bala R对OP问题的解决方案。我将您的解决方案转换为扩展方法,并修复了空值抛出错误的问题。

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

我修复了landi的答案,以适应子空对象和处理IEnumerable集合(循环遍历对象列表和修剪字符串属性)。我对他的回答做了一个编辑,因为不切题而被拒绝了,但那是一堆垃圾。希望这对某些人有所帮助,因为landi的答案并不适用于我拥有的每种对象类型。现在它做到了。

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

我已经写了一个扩展方法,它也照顾子类和引用类(如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();
            }
        }
    }
}

我不确定是否要改变访问器的行为。这听起来一点也不容易。如何将修剪添加到基类中呢?

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

(另外,请注意检查您的字段是否可读可写)

,

EDIT:然后您可以将这样的内容添加到基类中,并一次修剪所有这些内容。WeakReference类将允许您轻松地跟踪实例,而不会妨碍垃圾收集器:

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

您可以使用反射做这样的事情:

// 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[]{}));

感谢landi的解决方案。我修改了他的方法,增加了对带有索引参数的类的支持,并在继续之前检查对象是否为空。

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

扩展了Own的解决方案,并添加了可以写入属性的检查。有一些"未找到属性设置方法";

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

对于使用VB的人。. NET,我转换了thrawnis的答案,并添加了一个条件,只返回那些不是只读的属性。否则,如果你的类有只读属性,当你试图为这些属性设置SetValue时,你会得到运行时错误。

''' <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

你可以试试:

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

像这样使用:

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

我采纳了OwN的答案,但做了以下更改:

  • 使用提前退出来减少if
  • 的嵌套
  • 到处使用var,重命名一些变量
  • 新增单元测试

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

感谢@Teter28提供的代码生成的想法。这比反思的解决方案更有效。所提供的代码示例不起作用。这是一个准备使用的例子。

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

下面是一个支持嵌套集合和字符串的解决方案:

public static T TrimStringProperties<T>(this T input)
{
    if (input is null)
    {
        return input;
    }
    var props = input.GetType()
            .GetProperties(BindingFlags.Instance | BindingFlags.Public)
            .Where(prop => prop.GetIndexParameters().Length == 0)
            .Where(prop => prop.CanWrite && prop.CanRead);
    foreach (PropertyInfo prop in props)
    {
        var value = prop.GetValue(input, null);
        if (value is string stringValue && stringValue != null)
        {
            prop.SetValue(input, stringValue.Trim(), null);
        }
        else if (value is IEnumerable enumerable)
        {
            foreach (var item in enumerable)
            {
                TrimStringProperties(item);
            }
        }
    }
    return input;
}