修剪所有字符串属性

26

我需要剪裁一些对象中的字符串属性,但我不想一个个访问所有对象和属性,在设置属性时进行剪裁操作(有很多对象,超过300个,并且有很多字符串属性)。

一个提示:所有我的对象都有一个名为CoreTransaction的超类,因此我可以使用它(通过某种反射机制)更轻松地完成这件事。

这是否可能?


你想在运行时在给定的属性设置器中执行此操作,还是你想更改所有文件的源代码? - Oliver
13个回答

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

谢谢您的帮助,但我想要“更改”设置属性,这样当我执行myObject.propertyString =“foo”;时,属性的值将只是“foo”。 - rpf
@rpf,您正在尝试使用反射更改setter逻辑吗? - Bala R
@rpf 虽然这不是不可能,但我认为这不值得你的时间。这将涉及到深入研究IL代码生成(搜索ILGenerator)。 - Bala R
10
给我点赞,因为我不想在这里考虑逻辑。我唯一要补充的是,在 SetValue 行上可能需要进行空值检查,因为 currentValue.Trim() 会抛出 NullReferenceException :-) - Frito
2
真的很酷。我将其制作为对象扩展。除了在执行“SetValue”之前需要进行空值检查外,最好还要在最初获取属性时对lambda表达式进行“p.CanWrite”检查 - 这样我们就确保不会尝试在只读属性上设置值。 - Costin_T
我们能否跳过null,直接使用GetValueSetValue方法的分别带有1个和2个参数的重载呢? - shashwat

24

感谢Bala R为原帖问题提供的解决方案。我将您的解决方案转换为扩展方法,并解决了空值引发错误的问题。

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

谢谢。我还添加了一个用于子类的第二个foreach循环和一个称为IgnoreTrim的属性,您可以将其添加到属性中。 - Rhyous

11

我修改了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 https://dev59.com/UlLTa4cB1Zd3GeqPcaqc
                    else if (currentNodeType != typeof (object) && Type.GetTypeCode(currentNodeType) == TypeCode.Object)
                    {
                        p.GetValue(obj, null).TrimAllStrings();
                    }
                }
            }
        }
    }
}

1
我喜欢这个线程从上到下的改进链。 - TheLegendaryCopyCoder
这并没有涉及到字符串的IEnumerable集合。有没有一种方法可以在不检查确切类型(例如IEnumerable<string>,IReadOnlyCollection<string>,IList<string>)并将其替换为修剪后的字符串的新IEnumerable等的情况下完成呢? - user8607541
你应该将if语句改为(currentNodeType == typeof(System.String) && p.CanWrite),以确保属性是可写的。 - undefined

7

我编写了一个扩展方法,它还可以处理子类和引用类上的字符串(例如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 https://dev59.com/UlLTa4cB1Zd3GeqPcaqc
            else if (currentNodeType != typeof (object) && Type.GetTypeCode(currentNodeType) == TypeCode.Object)
            {
                p.GetValue(obj, null).TrimAllStrings();
            }
        }
    }
}

请注意,这是关于日期时间的内容。 - ErpaDerp

6

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

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

(此外,请注意检查您的字段是否可读可写。)
编辑:然后,您可以将以下内容添加到基类中,并一次性修剪所有内容。 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();
            }
        }
    }
}

4
您可以使用反射来实现类似以下的操作:
// 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[]{}));

2

扩展了Own的解决方案,并添加了检查是否可以对属性进行写操作。由于Uri属性,出现了一些“找不到属性设置方法”的错误。

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 https://dev59.com/UlLTa4cB1Zd3GeqPcaqc
                    else if (currentNodeType != typeof (object) && Type.GetTypeCode(currentNodeType) == TypeCode.Object)
                    {
                        p.GetValue(obj, null).TrimAllStrings();
                    }
                }
            }
        }
    }
}

2

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

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 https://dev59.com/UlLTa4cB1Zd3GeqPcaqc
            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();
                }
            }
        }
    }

1
感谢@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();
    }
}

1
对于使用VB.NET的人,我转换了thrawnis的答案并添加了一个条件,只返回那些不是只读的属性。否则,如果您的类具有只读属性,则在尝试为这些属性设置值时会出现运行时错误。
''' <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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接