5

我正在编写一个系统,它是程序员应用程序的基础,需要检测他们对某些数据的访问。我主要可以使用属性来做到这一点,如下所示:

public class NiceClass {
    public int x { get; set; }
}

然后我进入并调整getset访问器,以便它们适当地处理访问。然而,这要求用户(应用程序程序员)将他们的所有数据定义为属性。

如果用户想要使用具有“普通”字段(而不是属性)的预先存在的类,我无法检测到这些访问。例子:

public class NotSoNiceClass {
    public int y;
}

我无法检测到对y. 但是,我想允许使用预先存在的类。作为妥协,用户有责任在访问此类数据时通知我。例如:

NotSoNiceClass notSoNice;
...
Write(notSoNice.y, 0);  // (as opposed to notSoNice.y = 0;)

类似的东西。相信我,我已经对此进行了非常彻底的研究,甚至直接分析字节码以检测访问由于可能存在间接等原因是不可靠的。我确实需要用户通知我。

现在我的问题是:你能推荐一种“优雅”的方式来执行这些通知吗?(是的,我知道这整个情况一开始并不“优雅”;我试图不让它变得更糟;))。你会怎么做?

这对我来说是个问题,因为实际上情况是这样的:我有以下课程:

public class SemiNiceClass {
     public NotSoNiceClass notSoNice { get; set; }
     public int z { get; set; }
}

如果用户想要这样做:

SemiNiceClass semiNice;
...
semiNice.notSoNice.y = 0;

他们必须改为这样做:

semiNice.Write("notSoNice").y = 0;

WhereWrite将返回 的克隆notSoNice,这就是我希望set访问器做的事情。但是,使用字符串非常难看:如果稍后他们重构该字段,他们将不得不检查他们的Write("notSoNice")访问并更改字符串。

我们如何识别字段?我只能想到字符串、整数和枚举(也就是整数)。但:

  • 我们已经讨论过字符串的问题。
  • 整数很痛苦。它们甚至更糟,因为用户需要记住哪个 int 对应于哪个字段。重构同样困难。
  • 枚举(例如NOT_SO_NICEZ,即 的字段SemiNiceClass)易于重构,但它们要求用户为每个类(SemiNiceClass等)编写一个枚举,并为类的每个字段提供一个值。它很烦人。我不想让他们恨我;)

那么,我听到你问,为什么我们不能这样做(下)?

semiNice.Write(semiNice.notSoNice).y = 0;

因为我需要知道正在访问哪个字段semiNice.notSoNice,并且不识别字段。这是字段的值,而不是字段本身。

叹。我知道这很丑陋。相信我 ;)

我将非常感谢您的建议。

提前致谢!

(另外,我无法为这个问题想出好的标签。如果您有更好的想法,请告诉我,我会编辑它们)


编辑#1: Hightechrider 的建议:表达式。

我不知道是否Write(x =>semiNice.y, 0)是基于我为我的问题(SemiNiceClass等)编写的类,还是只是一个示例,但如果是前者,它与结构不匹配:y. SemiNiceClass你的意思是Write(x =>semiNice.notSoNice.y, 0)

我不确定您对我使用它的意思...我将发布我编写的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Test.MagicStrings {

    public class MagicStringsTest {
        public static void Main(string[] args) {
            SemiNiceClass semiNice = new SemiNiceClass();
            // The user wants to do:  semiNice.notSoNice.y = 50;
            semiNice.Write(x => semiNice.notSoNice.y, 50);
        }
    }

    public class SemiNiceClass {

        public NotSoNiceClass notSoNice { get; set; }
        public int z { get; set; }

        public SemiNiceClass() {
            notSoNice = new NotSoNiceClass();
        }

        public void Write(Func<object, object> accessor, object value) {
            // Here I'd like to know what field (y, in our example) the user wants to
            // write to.
        }

    }

    public class NotSoNiceClass {
        public int y;
    }

}

我如何获得该信息Write?我找不到任何方法从Func<,>. 另外,为什么写semiNice.Write(x => semiNice.notSoNice.y, 50);而不是semiNice.Write(() => semiNice.notSoNice.y, 50);,因为我们没有使用x任何东西?

谢谢。


编辑#2: Hans Passant 的建议:用属性替换字段。

这是我最初打算做的,但重新编译不是一种选择。


编辑#3:Ben Hoffstein 的建议:动态代理;林福。

我已经对此进行了漫长而艰难的研究,并且由于相对复杂的原因我无法使用它。解释太长了,但请放心:如果我可以使用它,我会的。它比我目前的解决方案要整洁得多。

4

3 回答 3

2

使用表达式,例如 Write(x =>semiNice.y, 0)

这种技术经常被用作避免魔术字符串的一种方法。

例如

    public void Write<T,U>(T source, Expression<Func<T, U>> lambda, U value) 
    {
        var body = lambda.Body as MemberExpression;
        string memberName = body.Member.Name;
        if (body.Member.MemberType == MemberTypes.Field)
        {
            (body.Member as FieldInfo).SetValue(source, value);
        }
        else if (body.Member.MemberType == MemberTypes.Method)
        {
            (body.Member as MethodInfo).Invoke(source, new object[]{value});
        }
        Console.WriteLine("You wrote to " + memberName + " value " + value);
    }
于 2010-04-29T17:45:19.993 回答
1

您是否考虑过使用动态代理来拦截对目标类的所有调用,做您需要做的任何事情,然后将调用转发到目标?

像 LinFu 这样的东西可能会成功:http: //www.codeproject.com/KB/cs/LinFuPart1.aspx

于 2010-04-29T17:50:41.340 回答
1

以下代码支持 Refractoring,无需编写额外的代码,但是由于反射,它可能执行得不是很快,但它肯定会让你访问你想要的。

你必须像下面这样玩一下表达式树,

public class MagicStringsTest
{
    public static void Main(string[] args)
    {
        SemiNiceClass semiNice = new SemiNiceClass();
        // The user wants to do:  semiNice.notSoNice.y = 50;

        semiNice.Write( t=>t.y , 50);

        Console.ReadLine();
    }
}

public class SemiNiceClass
{

    public NotSoNiceClass notSoNice { get; set; }
    public int z { get; set; }

    public SemiNiceClass()
    {
        notSoNice = new NotSoNiceClass();
    }

    public void Write<R>(Expression<Func<NotSoNiceClass,R>> exp, R value)
    {
        if (exp.Body.NodeType == ExpressionType.MemberAccess)
        {
            MemberExpression e = exp.Body as MemberExpression;
            Console.WriteLine("Writing value for " + e.Member.Name 
                + " of NotSoNiceClass");
            FieldInfo info = e.Member as FieldInfo;

            // value is set using reflection...
            info.SetValue(notSoNice, value);
        }
        else
        {
            // throw exception, expecting of type x=>x.y
        }
    }


}
于 2010-04-30T08:28:05.960 回答