识别场地的“优雅”方式?

时间:2010-04-29 17:40:18

标签: c# field notifications identifier magic-string

我正在编写一个系统,它是程序员应用程序的基础,需要检测他们对某些数据的访问权限。我几乎可以使用属性这样做:

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;

Write将返回notSoNice的克隆,这是我希望set访问者无论如何都要做的。但是,使用字符串非常难看:如果稍后他们重构字段,他们将不得不重新访问Write("notSoNice")访问并更改字符串。

我们如何识别该领域?我只能想到字符串,整数和枚举(即再次注入)。但是:

  • 我们已经用字符串讨论了这个问题。
  • Ints很痛苦。它们甚至更糟,因为用户需要记住哪个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的建议:动态代理;李林甫。

我已经深入研究了这个问题,而且由于相对复杂的原因我无法使用它。这可能太长了解释,但请放心:如果我可以使用它,我会的。它比我目前的解决方案更加整洁。

3 个答案:

答案 0 :(得分:2)

使用表达式,例如写(x =&gt; semiNice.y,0)

此技术通常用作避免魔术字符串的方法。

e.g。

    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);
    }

答案 1 :(得分:1)

您是否考虑使用动态代理拦截对目标类的所有调用,执行您需要执行的操作,然后将调用转发给目标?

LinFu之类的东西可以解决问题:http://www.codeproject.com/KB/cs/LinFuPart1.aspx

答案 2 :(得分:1)

以下代码支持Refractoring,无需编写其他代码,但由于反射可能无法执行,但它肯定会让您访问所需内容。

您必须按照以下方式使用Expression树

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
        }
    }


}