从输入字符串中获取属性名称作为字符串

4

我正在创建一个模块,使用C#和Gembox.Document在Word中创建合并字段。首先,我想说这是我被分配的任务,所以无论这是否是一种不好的方式,这都是他们想要的方式。

我有一个Windows窗体应用程序,其中有一个文本框和一个按钮。在文本框中,他们希望可以粘贴dto/model,例如:

"public class Example 
{
    public string Name { get; set; }
    public string Surname { get; set; }
    public string Cellphone { get; set; }
    public string Address { get; set; }
    public string CompanyName { get; set; }
    public DateTime CurrentDate { get; set; }  
}"

我已经有将合并字段添加到Word文档的逻辑,使用一个方法,我传入一个包含所有合并字段名称的字符串数组。
问题是:我需要能够以某种方式对上面的大字符串进行子串操作,在文本框中包含一个作为字符串的DTO/模型,以获取属性名称并将其添加到string[]中,因为它们将是合并字段名称。
我希望我已经足够清楚地解释了自己的问题。这是我在这里的第一个问题,我不习惯用英语解释我的问题。
编辑: 为了说明问题:我需要从该字符串中获取属性名称,并将它们放入一个string[]中。
string s = @"public string Name { get; set; }
  public string Surname { get; set; }
  public string Cellphone { get; set; }
  public string Address { get; set; }
  public string CompanyName { get; set; }
  public DateTime CurrentDate { get; set; }"

你是否正在尝试从字符串中解析类型? - Amit Kumar Ghosh
1
我猜你可以用Roslyn解析它。或者如果你确定格式总是这么简单,可以使用正则表达式,如\w+(?=\s*\{\s*get\b) - Lucas Trzesniewski
如果我没记错的话,你希望你的应用程序能够像编译器一样进行识别。因此最好你去了解一些相关的内容。 - Bharadwaj
@Jokerish 使用.Matches而不是.Match - 正则表达式可以正常工作(请参见此处),但这是解决问题的一种笨拙方法。 - Lucas Trzesniewski
这种方法不够严谨,因此它是hacky的。例如,这种方法会选择被注释掉的属性。如果您想要一个严谨的解决方案,请使用C#解析器(Roslyn有一个)。 - Lucas Trzesniewski
显示剩余5条评论
3个回答

3

我认为你应该使用解析器而不是自己的解决方案来解析这段文本,然后搜索语法树以找到属性名称。我想到了类似于这样的东西:

使用NRefactory分析C#代码

这段代码返回完整的语法树或错误(我使用的是NRefactory但你可以使用Roslyn):

var parser = new CSharpParser();
var syntaxTree = parser.Parse(programCode);

然后搜索syntaxTree字段以获取属性。

示例代码:

const string code = @"public class Example {
    public string Name { get; set; }
    public string Surname { get; set; }
    public string Cellphone { get; set; }
    public string Address { get; set; }
    public string CompanyName { get; set; }
    public DateTime CurrentDate { get; set; }
}";
var syntaxTree = new CSharpParser().Parse(code, "program.cs");
var listOfPropertiesNames = syntaxTree.Children
    .SelectMany(astParentNode => astParentNode.Children)
    .OfType<PropertyDeclaration>()
    .Select(astPropertyNode => astPropertyNode.Name)
    .ToList();

这个片段提取属性名称。

我不确定我解释得足够清楚,请检查我的编辑。 - onmi
@Jokerish 这回答了你的问题,我认为使用C#解析器是最佳方法。 - Lucas Trzesniewski
你能给我一个更精确的例子,告诉我如何使用这个方法来实现我想要的吗?我对这些东西还不是很熟悉。 - onmi
这个答案连同正则表达式"\w+(?=\s{\sget\b)"一起解决了我的问题。谢谢,伙计。真的帮了我很大忙。 - onmi

1
您可以使用CSharpCodeProvider类将代码编译为程序集,然后使用反射在已编译的程序集中查找类型。
var sourcePart = @"public class Example 
{
    public string Name { get; set; }
    public string Surname { get; set; }
    public string Cellphone { get; set; }
    public string Address { get; set; }
    public string CompanyName { get; set; }
    public DateTime CurrentDate { get; set; }
}";

    var sourceTemplate = @"using System;

    @code

";

var code = sourceTemplate.Replace("@code", sourcePart);

CSharpCodeProvider c = new CSharpCodeProvider();

CompilerParameters cp = new CompilerParameters();

CompilerResults cr = c.CompileAssemblyFromSource(cp, code);
if (cr.Errors.Count > 0)
{
    MessageBox.Show("ERROR: " + cr.Errors[0].ErrorText,
        "Error evaluating cs code", MessageBoxButtons.OK,
           MessageBoxIcon.Error);
    return;
}

var a = cr.CompiledAssembly;

var type = a.GetTypes().Single();

string[] propertyNames = type.GetProperties().Select(p => p.Name).ToArray();

更新:

请记住,应用程序域中加载的类型无法卸载,并且将一直消耗内存,直到应用程序退出。

因此,如果用户经常使用此功能,则会逐步消耗内存。

如果这成为问题,您可以通过创建单独的应用程序域或生成另一个进程来提供此功能来解决此问题,但这是另一个问题。


我不确定我解释得足够清楚,请检查我的编辑。 - onmi
更新了答案,现在你可以将属性存储在一个字符串数组中。 - George Polevoy

1
你可以创建自定义静态方法来解析文本。它的功能是跳过字符串,从一个 '{' 的索引到下一个索引,并向后检查是否有 '(' 或 ')' 字符(这表明它是一个方法而不是属性,应该跳过它),并向后查找属性的开头。之后提取值,然后跳转到下一个 '{' 字符的索引,以此类推。
    static string[] GetProperties(string dirty)
    {
        List<string> properties = new List<string>();
        int i = dirty.IndexOf("{ ");
        StringBuilder sb = new StringBuilder();
        int propEndIndex = -1; int i2 = -1;

        for (; i != -1; i = dirty.IndexOf("{ ", i + 1))
        {
            i2 = i - 1;

            for (; dirty[i2] == ' '; i2--) { }

            if (dirty[i2] == '(' || dirty[i2] == ')') continue;

            propEndIndex = i2 + 1;

            for (; dirty[i2] != ' '; i2--) { }                

            for (i2++; i2 < propEndIndex; i2++)
                sb.Append(dirty[i2]);                

            properties.Add(sb.ToString());
            sb.Clear();
        }

        return properties.ToArray();
    }

使用示例:

       Stopwatch sw = new Stopwatch();

        var s = @"public class Example 
                {
                   public string Name { get; set; }
                   public string Surname { get; set; }
                   public string Cellphone { get; set; }
                   public string Address { get; set; }
                   public string CompanyName { get; set; }
                   public DateTime CurrentDate { get; set; }  

                   public void MyMethod() { }
                }";

        sw.Start();

        string[] props =  GetProperties(s);

        sw.Stop();

        foreach (var item in props)
            Console.WriteLine(item);

        Console.WriteLine("\n\nMethod is executed in " + sw.ElapsedMilliseconds + " ms");

        Console.ReadKey();

输出:
Name
Surname 
CellPhone
Address
CompanyName 
CurrentDate

Method is executed in 1 ms

@Bauss 无论它看起来如何,其性能都优于 LINQ。它使用简单的 for 循环,时间复杂度大约为 O(n) - Fabjan
是的,我并没有说它表现不好,我只是在谈论“外观”方面。 - Bauss
非常感谢您为解决我的问题做出的贡献。虽然我已经找到了解决方法,但是在上面的答案帮助下,我认为这种方法也可以解决我的问题,并且我希望有一天它能帮助其他人解决他们的问题。 - onmi

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