C# 反射:替换文本中所有属性出现的值

9

我有一个类似于

public class MyClass {

  public string FirstName {get; set;}
  public string LastName {get; set;}

}

情况是这样的,我有一个字符串文本,如下:

string str = "My Name is @MyClass.FirstName @MyClass.LastName";

我想要的是使用反射将@MyClass.FirstName@MyClass.LastName替换为与类中对象FirstName和LastName分配的值。 请问有什么帮助吗?

你是否在寻找 string.Format("My Name is {0} {1}", obj.FirstName, obj.LastName) 这段代码?或者,如果你用的是 C# 6,更好的写法是 $"My Name is {obj.FirstName} {obj.LastName}" - Kvam
我想要使用反射而不是字符串格式化(string.Format)来完成。 - Aqdas
@MyClass 是无用的:为了获取实例(即非static)的属性值,您必须提供该实例:MyClass my = ... .GetValue(my); 有了这个实例,您可以通过简单的 my.GetType() 获取类型(MyClass)。 - Dmitry Bychenko
5个回答

9
如果您想要生成字符串,您可以使用Linq来枚举属性:
  MyClass test = new MyClass {
    FirstName = "John",
    LastName = "Smith",
  };

  String result = "My Name is " + String.Join(" ", test
    .GetType()
    .GetProperties(BindingFlags.Public | BindingFlags.Instance)
    .Where(property => property.CanRead)  // Not necessary
    .Select(property => property.GetValue(test)));

  // My Name is John Smith
  Console.Write(result);

如果你想在字符串中进行替换(类似格式化),则正则表达式可以成为解析字符串的良好选择:

  String original = "My Name is @MyClass.FirstName @MyClass.LastName";
  String pattern = "@[A-Za-z0-9\\.]+";

  String result = Regex.Replace(original, pattern, (MatchEvaluator) ((match) => 
    test
      .GetType()
      .GetProperty(match.Value.Substring(match.Value.LastIndexOf('.') + 1))
      .GetValue(test) 
      .ToString() // providing that null can't be returned
  ));

  // My Name is John Smith
  Console.Write(result);

请注意,为了获取实例(即非静态)属性的值,您必须提供实例(上面代码中的test):
   .GetValue(test) 

所以字符串中的 @MyClass 部分是无用的,因为我们可以直接从实例中获取类型:

   test.GetType()

编辑:如果某些属性的返回值可能null

 String result = Regex.Replace(original, pattern, (MatchEvaluator) ((match) => {
   Object v = test
     .GetType()
     .GetProperty(match.Value.Substring(match.Value.LastIndexOf('.') + 1))
     .GetValue(test);

   return v == null ? "NULL" : v.ToString(); 
 }));

很棒的解决方案。我相信使用正则表达式会比循环对象更有效率。我有一个问题。如果我不知道我的类是什么类型,即它可以是MyCar、MyClass或MyHome,而且我也不知道字符串的确切含义,在这种情况下,任何类型的类都可以在提供给原始对象的字符串中查找和替换其属性。对此有什么想法吗? - Aqdas
@Aqdas: 仅使用"MyClass"字符串以及"MyCar""MyHome"不够的;为了读取实例属性,您必须提供实例。想象一下,您有三个具有不同属性值的MyClass实例- my1,my2,my3。应该使用哪个实例?在上面的代码中,对于每个匹配项,您都必须提供该实例(将逻辑放置在我放置test的位置)。 - Dmitry Bychenko
@Aqdas: 那你可以提供一下这个函数的签名吗?类似于 String MyFunc(String line, Object instance); - Dmitry Bychenko
我已经完成了,并且这个解决方案对我来说完美无缺。只有一个问题,我想问你: // 假定不会返回 null 你能详细解释一下吗?或者有任何解决方法吗? - Aqdas
函数签名如下: public async Task<string> GetData<T>(string param, T type) {} 其中param = 包含@MyClass.FirstName的字符串文本,T是类型,表示任何调用类。 - Aqdas
显示剩余5条评论

8

首先,我建议在其他选项(如string.Format)可行时避免使用反射。反射可能会使你的代码不易读和难以维护。无论哪种方式,你可以这样做:

public void Main()
{
    string str = "My Name is @MyClass.FirstName @MyClass.LastName";
    var me = new MyClass { FirstName = "foo", LastName = "bar" };
    ReflectionReplace(str, me);
}

public string ReflectionReplace<T>(string template, T obj)
{    
    foreach (var property in typeof(T).GetProperties())
    {
        var stringToReplace = "@" + typeof(T).Name + "." + property.Name;
        var value = property.GetValue(obj);
        if (value == null) value = "";
        template = template.Replace(stringToReplace, value.ToString());
    }
    return template;
}

如果您想向类中添加新属性并更新模板字符串以包含新值,则不需要进行其他更改。它还应该处理任何类上的属性。


好的答案。我还有一个问题想在这里问,假设我不知道对象的类型,即类的名称是什么,我需要替换多少个对象?那么在这种情况下,如果类型是MyCar或者类型是MyHome,我希望所有类似于MyClass.FirstName的值都会更新到字符串文本对象中。 - Aqdas
我的意思是,我不知道类的类型,也不知道要替换的字符串文本中有什么内容,只知道如果字符串文本具有相同类型的所有属性,则需要用其分配的值进行替换。 - Aqdas
我更新了答案以处理任何类型的对象(具有属性)。 - Kvam
旧版本的.NET使用...GetValue(obj, null); - scotru

2
使用反射,您可以像下面展示的那样实现它。
MyClass obj = new MyClass() { FirstName = "Praveen", LaseName = "Paulose" };

        string str = "My Name is @MyClass.FirstName @MyClass.LastName";

        string firstName = (string)obj.GetType().GetProperty("FirstName").GetValue(obj, null);
        string lastName = (string)obj.GetType().GetProperty("LaseName").GetValue(obj, null);

        str = str.Replace("@MyClass.FirstName", firstName);
        str = str.Replace("@MyClass.LastName", lastName);

        Console.WriteLine(str);

首先,您需要使用 GetProperty 找到相关的属性,然后使用 GetValue 获取其值。

更新

根据评论中进一步的澄清,您可以使用正则表达式来识别字符串中的所有占位符,例如 @MyClass.Property。一旦找到它们,您可以使用 Type.GetType 来获取类型信息,然后使用上面显示的代码来获取属性。但是,您需要命名空间来实例化类型。


好的回答,我这里有一个关于这个解决方案的问题。 是否有一种方法,可以不使用String.Replace,而是做一些类似于在同一文本中更改@MyClass.FirstName的事情?例如,Myclass有100多个属性,有时文本有三个值,有时有50多个。所以有没有办法,将文本中所有这些值替换为属性分配的值?感谢您的回复。 - Aqdas
@Aqdas 你有什么问题? - Praveen Paulose
@Aqdas 你可以使用正则表达式来识别字符串中的所有占位符,例如 MyClass.Property。一旦找到它们,你可以使用 Type.GetType 来获取类型信息,然后使用上面展示的代码来获取属性。但是你需要命名空间来实例化这些类型。 - Praveen Paulose
@Parveen,你能否给我提供一个使用正则表达式替换多个对象的例子? - Aqdas

0
最近在一个项目中,我需要类似的东西。要求使用占位符格式,这种格式不会自然地出现在我们的文本中,例如@obj.property可能会使用@obj.property@。此外,还需要处理级联令牌(依赖于令牌的令牌)和来自IEnumerable的数据处理,例如:["Cat", "Dog", "Chicken"]需要在文本中显示为"Cat, Dog, Chicken"。
我已经创建了一个,并有一个NuGet包,可以更快地将此功能添加到您的代码中。

0

虽然这篇文章已经五年了,但我发现它很有用。

这是我对Dmitry Bychenko的适应,也可以深入到任何JSON字段。

目前只能在一个级别上工作,但我会在某个时候将其递归。

Regex _replacementVarsRegex = new Regex(@"\{\{([\w\.]+)\}\}", RegexOptions.Compiled);
var result = "string with {{ObjectTypeName.PropertyName}} and json {{ObjectTypeName.JsonAsStringProperty.JsonProperty}}";
string ReplaceFor(object o) => _replacementVarsRegex.Replace(result, match =>
{
    if (o == null) return match.Value;
    var typeName = match.Value.Substring(0, match.Value.IndexOf('.')).TrimStart('{');
    var type = o.GetType();
    if (typeName != type.Name) return match.Value;
    var name = match.Value.Substring(match.Value.LastIndexOf('.') + 1).TrimEnd('}');
    var propertyInfo = type.GetProperty(name);
    var value = propertyInfo?.GetValue(o);
    var s = value?.ToString();
    if (match.Value.Contains(".Metadata."))
    {
        var jsonProperty = type.GetProperty("Metadata");
        var json = jsonProperty?.GetValue(o)?.ToString() ?? string.Empty;
        if (!string.IsNullOrWhiteSpace(json))
        {
            // var dObj = JsonConvert.DeserializeObject(json);
            var jObj = JObject.Parse(json);
            var val = jObj[name].Value<string>();
            s = val;
        }
    }

    return s ?? match.Value;
});

result = ReplaceFor(object1);
result = ReplaceFor(object2);
//...
result = ReplaceFor(objectn);

//remove any not answered, if desired
result = _replacementVarsRegex.Replace(result, string.Empty);

欢迎提出改进建议!


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