【问题标题】:How can I add CustomAttribute to all object properties dynamically?如何动态地将 CustomAttribute 添加到所有对象属性?
【发布时间】:2019-07-23 13:34:56
【问题描述】:

我有一个具有多个属性的对象,如下所示:

public int num1 { get; set; }

public int num2 { get; set; }

public string str1 { get; set; }

public string str2 { get; set; }

这些属性位于一个动态生成的类中,因此它将清除它们上的所有 CustomAttributes。

我尝试添加

[Submit]
MyClass myObject

但它没有传播到我的对象属性上

有没有办法在 c# 中动态地做到这一点?

【问题讨论】:

  • 这些是属性,而不是属性。
  • 你是如何动态生成你的类的?请分享该代码。
  • 我已经编辑了我的问题,用属性替换属性

标签: c#


【解决方案1】:

我在理解你的问题时遇到了一些困难,但让我试着澄清一下。

我有一个具有多个属性的对象 ... 它将清除所有 CustomAttributes

在 C# 中,<access> <type> <name> { get; set; } 格式的类成员被称为“属性”而不是“属性”。另一方面,“属性”是注释的 C# 实现,例如您所指的自定义属性。

也就是说,我目前理解您的意思是您有一个自动生成的具有多个属性的类。您希望这些属性中的每一个都有自己的自定义属性,但是如果您编辑类,它们会在下次生成时被删除,并且您无法让类生成器包含自定义属性。

了解更多课程背景可能会有所帮助。例如,它是如何生成的?如果它是一个实体框架类,下面的 SO 问题可能会提供一些见解: Add data annotations to a class generated by entity framework。一般来说,是(或者你可以制作)生成的类partial吗?如果是这样,那么您仍然可以按照上述问题答案中的方法,viz. 制作自己的部分类实现,以提供属性的自定义属性。

例如,如果您生成的类看起来(或可以看起来)像这样:

/// <auto-generated />
public partial class MyClass
{
    public int Num1 { get; set; }

    public int Num2 { get; set; }

    public string Str1 { get; set; }

    public string Str2 { get; set; }
}

您可以使用自定义注释编写分部类的另一部分,如下所示:

/// human generated
public partial class MyClass
{
    [Submit]
    public int Num1 { get; set; }

    [Submit]
    public int Num2 { get; set; }

    [Submit]
    public string Str1 { get; set; }

    [Submit]
    public string Str2 { get; set; }
}

同样,在不了解您的情况的情况下,我不确定这是否为您提供了所需的信息,但我希望它至少可以为您提供一个起点。

编辑

如果该类不是部分的,您可以考虑使用一个包装属性使用自定义属性的类来包装您生成的类。例如,

/// human generated
public class MyClassWrapper
{
    private readonly MyClass wrapped;

    public MyClassWrapper(MyClass wrapped)
    {
        this.wrapped = wrapped;
    }

    [Submit]
    public int Num1 { get => this.wrapped.Num1; set => this.wrapped.Num1 = value; }

    [Submit]
    public int Num2 { get => this.wrapped.Num2; set => this.wrapped.Num2 = value; }

    [Submit]
    public string Str1 { get => this.wrapped.Str1; set => this.wrapped.Str1 = value; }

    [Submit]
    public string Str2 { get => this.wrapped.Str2; set => this.wrapped.Str2 = value; }
}

编辑 2

如果您希望以一些设计和运行时复杂性为代价获得更动态的解决方案,您可以考虑这个 SO 问题:How to add property-level Attribute to the TypeDescriptor at runtime?。它似乎解决了类似的问题--

真的,它是为 MS 的应用程序设置生成代码的,所以你不能以任何方式扩展它的属性。

我不会在这里完全重复 Gman 的解释,但基本上这种方法包括

  1. 获取类型(MyClass)或myObject类型的实例
  2. 使用TypeDescriptor.GetProvider(MyClass/myObject).GetTypeDescriptor(MyClass/myObject)获取类型或对象的基线ICustomTypeDescriptor
  3. 用这个基线描述符构造他的PropertyOverridingTypeDescriptor
  4. 使用TypeDescriptor.GetProperties(MyClass/myObject) 遍历MyClass/myObject 的属性定义。使用TypeDescriptor.CreateProperty 根据当前属性的定义创建一个新的属性定义,添加自定义属性EditorAttribute(或在您的情况下为SubmitAttribute),并使用3.中构造的PropertyOverridingTypeDescriptor 来使用新属性定义。
  5. 用3中构造的PropertyOverridingTypeDescriptor构造他的TypeDescriptorOverridingProvider
  6. 将新的属性定义应用于MyClass/myObjectTypeDescriptor.AddProvider

【讨论】:

  • 感谢您的回答,我无法修改生成类的方式,这是通过使用我工作的工具。
  • 好的。我想那么班级不是partial
  • 不,不是partial
  • 您的答案是我为我的问题找到的唯一解决方案,但如果我是正确的,每次我在 MyClass 中添加属性时,我都必须在 MyClassWrapper 中添加新属性?我会再等一会儿,看看是否有人对此有其他解决方案,否则我会将您的答案标记为解决方案
  • 我已经更新了我的答案,参考了另一个面临类似情况的 SO'er 使用的更动态的方法。
【解决方案2】:

向动态生成的类添加属性的另一种方法是向代码生成管道添加一个命令行应用程序。

下面是一个如何用Microsoft.CodeAnalysis.CSharp库重写C#代码文件的例子:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System;
using System.IO;

namespace SB.Common.ContractGenerator
{
    class SubmitAttributeAdder : CSharpSyntaxRewriter
    {
       public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node)=>
            node.WithAttributeLists(
                node.AttributeLists.Count == 0
                    ? node.AttributeLists.Add(SyntaxFactory.AttributeList()
                         .AddAttributes(SyntaxFactory.Attribute(
                            SyntaxFactory.ParseName("Submit")))
                        // Add some whitespace to keep the code neat.
                        .WithLeadingTrivia(node.GetLeadingTrivia())
                        .WithTrailingTrivia(SyntaxFactory.Whitespace(Environment.NewLine)))
                    : node.AttributeLists);
    }

    class Program
    {
        static void Main(string[] args)
        {
            // The file name to be injected as a command line parameter
            var tree = CSharpSyntaxTree.ParseText(
                SourceText.From(File.ReadAllText("Test.cs")));

            File.WriteAllText("Test.cs",
                new SubmitAttributeAdder().Visit(tree.GetRoot()).ToString());
        }
    }
}

首先,输入的 C# 代码文件被解析为语法树,然后 SubmitAttributeAdder 类浏览所有声明的类及其属性,为每个类修改一个属性列表。

之后,修改后的语法树被保存回同一个文件。

这里应用程序只添加Submit 属性以防属性的属性列表为空,但可以轻松地使逻辑更复杂 - 例如检查是否还有其他属性,为Submit属性添加对应的using &lt;namespace&gt;等。

运行应用程序之前的Test.cs 文件示例:

namespace MyProject
{
    class MyClass
    {
        public int num1 { get; set; }

        public int num2 { get; set; }

        public string str1 { get; set; }

        public string str2 { get; set; }
    }
}

...之后:

namespace MyProject
{
    class MyClass
    {
        [Submit]
        public int num1 { get; set; }

        [Submit]

        public int num2 { get; set; }

        [Submit]

        public string str1 { get; set; }

        [Submit]

        public string str2 { get; set; }
    }
}

【讨论】:

  • 捋胡须 有趣。很有意思。因此,我的 Edit 2 中的链接答案会在运行时自动执行此操作,但这对于制作设计时二次生成工具可能会更好。
  • @Bondolin 是的,这仅适用于您在编译前生成代码时。例如,我使用这种方法来修改从 protobuf 生成的 C# 代码。
猜你喜欢
  • 2012-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-05
  • 2011-10-05
相关资源
最近更新 更多