修剪所有字符串属性
本文关键字:属性 字符串 修剪 | 更新日期: 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;
}