【问题标题】:Intercept call to property get method in C#在 C# 中拦截对属性获取方法的调用
【发布时间】:2010-04-22 14:05:57
【问题描述】:

假设我们有这个类:

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
}

现在,在 C# 中是否可以拦截对属性 get 方法的调用,运行其他方法并返回该方法的结果而不是属性值?我希望能够在幕后做一些额外的逻辑。 缺点是此类无法更改(在 C# 级别上)。也许是一些 IL ?

【问题讨论】:

  • 你能继承类并覆盖属性吗?
  • 其实这里的人并没有从上到下阅读它并发布了原始类可以更改的代码
  • 是的,大多数人都发布了最简单的解决方案,但我确实需要在 C# 引擎盖下注入代码。我认为 PostSharp 看起来不错。
  • 我也刚刚添加了一个 IL 往返示例。这将向您展示如何在没有第三方工具的情况下做到这一点。只是 .NET SDK。

标签: c# .net cil dynamic


【解决方案1】:

查看 PostSharp:http://www.sharpcrafters.com/

这完全实现了您正在寻找的东西,以及更多。

PostSharp 为 .NET 实现了面向方面的编程。它是一种商业产品。很酷。

请注意,此问题的任何解决方案都将涉及一些运行时开销(一些内存,一些额外的时间来进行函数调用。)没有什么是绝对免费的。

【讨论】:

  • 不,他说“无法更改(在 C# 级别)。也许是一些 IL?”
  • PostSharp 不会更改类的源代码。它改变了编译类产生的 IL;我相信原始海报对更改 IL 持开放态度。
  • 我的评论是针对您的答案的原始评论者,而不是您的答案(我同意)。我回复的评论已被删除,看起来我正在回复你。抱歉,我没有向该人澄清。
【解决方案2】:

如果您的类无法更改,您可以考虑使用 PostSharp 之类的工具 - 它允许您编写拦截或替换对方法和属性的调用的代码。

如果您可以更改代码,那么只需避免使用自动属性,并显式实现 get 和 set 操作。然后你可以做任何你需要的事情。或者,您可以将属性定义为 virtual 并派生另一个类来更改 getter 和/或 setter 的行为:

public class Person 
{ 
  public virtual int Id { get; set; } 
  public virtual string Name { get; set; } 
} 

public class PersonDerived : Person
{
  public override int Id { get { ... } set { ... } }
  public override string Name { get { ... } set { ... } }
}

如果您只需要它来进行单元测试,您可能需要研究像 MoqTypeMock 这样的模拟工具。

【讨论】:

    【解决方案3】:

    .NET SDK 已经具备您所需要的功能。如果你小心的话,你可以通过反汇编/重组(ildasm.exe 和 ilasm.exe)来往返 CLR 上的大多数东西。确保启动“Visual Studio 命令提示符”,以便这些工具位于您的 PATH 中。

    1) ildasm person.exe

    2) 文件 -> 转储 (person.il)

    3) 编辑修改get_Name()方法: 重要提示:在 IL 级别修改方法时,请始终重新计算 .maxstack,因为 Visual Studio .NET 编译器会为大多数方法准确设置它,如果添加任何代码,请提高 .maxstack 来处理该方法的运行时堆栈上的最大值#,否则您将获得无效程序。

      .method public hidebysig specialname instance string get_Name() cil managed
      {
        .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
        // Code size       11 (0xb)
        .maxstack  2
        .locals init (string V_0)
        IL_0000:  ldarg.0
        IL_0001:  ldfld      string Person::'<Name>k__BackingField'
        IL_0006:  stloc.0
        IL_0009:  ldloc.0
            IL_000a:  ldstr "IL code inserted here"
            call    void [mscorlib]System.Console::WriteLine(string)
        IL_001a:  ret
     } // end of method Person::get_Name
    

    4) 重新组装:ilasm person.il

    【讨论】:

    • 好的,谢谢。但是让我们假设我需要基于一些元数据生成这个类(类似于基于表描述的 ORM 生成类,但它不是 ORM :P)。现在最终用户可以在他的代码中使用这个类,我希望能够在这种情况下动态地注入方法调用(当然他使用了我的某种框架)。我希望你明白我的意思:)
    • 当然,但这是另一个问题(鉴于您在上面提出的问题)。您说 C# 无法更改,因此假设您是该课程的最终用户。如果您从 ORM 生成它,那么您就是原始类的创建者。
    【解决方案4】:

    扩展课程并做

    public class MyPerson : Person
    {
        public new int Id 
        { 
            get
            {
                foo();
                return base.Id;
            }
    }
    

    【讨论】:

    • 这不会编译。如果您更正大小写并返回this.Id,您将得到StackOverflowException(因为您一遍又一遍地调用相同的属性)。它需要调用base.Id,但这仅在代码将对象引用为MyPerson 而从不引用为Person 时才有效。
    【解决方案5】:

    没有自动属性,没有。

    但你可以这样做:

    public class Person
    {
        private int _id;
    
        public int Id
        {
            get
            {
                // Do some logic or call a method here
                return _id;
            }
            set
            {
                if(value <= 0)
                    throw new CustomInvalidValueException();
    
                _id = value;
            }
        }
    }
    

    【讨论】:

      【解决方案6】:
      public Class Person
      {
          private int _id;
          public int ID
          {
            get{ MyMethod(); return _id; }
            set{ _id = value; }
          }
         // etc. etc. .....
      
       }
      

      【讨论】:

        【解决方案7】:
        私人布尔 _pp; 公共布尔属性 { 获取 { // 你的代码} 设置 { _pp = 值; } }

        【讨论】:

          【解决方案8】:

          我认为您正在寻找Aspect Oriented Framework

          【讨论】:

            【解决方案9】:

            如果您无法更改类本身,您可以考虑使用扩展方法来获取名称。这将改变您访问数据的方式,因此它也可能不是一个选项。如果你有兴趣,你会这样做:

            public static class PersonExtensions
            {
                public static string GetName(this Person p)
                {
                    foo();
                    return p.Name;
                }
            }
            

            然后您可以通过这种方式访问​​该名称:

            Person myPerson = new Person();
            string name = myPerson.GetName();  // Also calls foo() for you extra processing
            

            希望这会有所帮助。

            【讨论】:

            • 谢谢,但我需要原样提供这个类,最终用户可以像使用其他“普通”类一样使用它。我知道这很难:)
            猜你喜欢
            • 2015-04-29
            • 1970-01-01
            • 1970-01-01
            • 2010-12-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多