一个“优雅”的人识别字段的方法?

发布于 2024-08-30 09:42:41 字数 3645 浏览 2 评论 0原文

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

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") 访问并更改字符串。

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

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

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

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

因为我需要知道正在访问什么字段,而semiNice.notSoNice无法识别字段。这是字段的价值,而不是字段本身。

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

我将非常感谢建议。

提前致谢!

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


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

我不知道 Write(x =>semiNice.y, 0) 是否是基于我为问题编写的类(SemiNiceClass 等)或者,如果它只是一个示例,但如果是前者,则它与结构不匹配:SemiNiceClass 中没有 y 字段。您的意思是 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;
    }

}

How do I get that info in Write?我找不到任何方法从 Func<,> 中提取该信息。另外,为什么要写 semiNice.Write(x => semiNice.notSoNice.y, 50); 而不是 semiNice.Write(() => semiNice.notSoNice.y, 50) ;,因为我们没有使用 x 来做任何事情?

谢谢。


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

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


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

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

I'm writing a system that underlies programmer applications and that needs to detect their access to certain data. I can mostly do so with properties, like this:

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

Then I go in and tweak the get and set accessors so that they handle the accesses appropriately. However this requires that the users (application programmers) define all of their data as properties.

If the users want to use pre-existing classes that have "normal" fields (as opposed to properties), I cannot detect those accesses. Example:

public class NotSoNiceClass {
    public int y;
}

I cannot detect accesses to y. However, I want to allow the use of pre-existing classes. As a compromise the users are responsible for notifying me whenever an access to that kind of data occurs. For example:

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

Something like that. Believe me, I've researched this very thoroughly and even directly analysing the bytecode to detect accesses isn't reliable due to possible indirections, etc. I really do need the users to notify me.

And now my question: could you recommend an "elegant" way to perform these notifications? (Yes, I know this whole situation isn't "elegant" to begin with; I'm trying not to make it worse ;) ). How would you do it?

This is a problem for me because actually the situation is like this: I have the following class:

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

If the user wants to do this:

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

They must instead do something like this:

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

Where Write will return a clone of notSoNice, which is what I wanted the set accessor to do anyway. However, using a string is pretty ugly: if later they refactor the field they'll have to go over their Write("notSoNice") accesses and change the string.

How can we identify the field? I can only think of strings, ints and enums (i.e., ints again). But:

  • We've already discussed the problem with strings.
  • Ints are a pain. They're even worse because the user needs to remember which int corresponds to which field. Refactoring is equally difficult.
  • Enums (such as NOT_SO_NICE and Z, i.e., the fields of SemiNiceClass) ease refactoring, but they require the user to write an enum per class (SemiNiceClass, etc), with a value per field of the class. It's annoying. I don't want them to hate me ;)

So why, I hear you ask, can we not do this (below)?

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

Because I need to know what field is being accessed, and semiNice.notSoNice doesn't identify a field. It's the value of the field, not the field itself.

Sigh. I know this is ugly. Believe me ;)

I'll greatly appreciate suggestions.

Thanks in advance!

(Also, I couldn't come up with good tags for this question. Please let me know if you have better ideas, and I'll edit them)


EDIT #1: Hightechrider's suggestion: Expressions.

I don't know if Write(x =>semiNice.y, 0) was meant to be based on the classes I wrote for my question (SemiNiceClass, etc) or if it's just an example, but if it's the former it doesn't match the structure: there's no y field in SemiNiceClass. Did you mean Write(x =>semiNice.notSoNice.y, 0)?

I'm not sure how you mean for me to use this... I'll post the code I've written:

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

}

How do I get that info in Write? I can't find any way to extract that information out of the Func<,>. Also, why write semiNice.Write(x => semiNice.notSoNice.y, 50); instead of semiNice.Write(() => semiNice.notSoNice.y, 50);, since we're not using x for anything?

Thanks.


EDIT #2: Hans Passant's suggestion: replacing fields with properties.

This is what I originally intended to do, but recompiling is not an option.


EDIT #3: Ben Hoffstein's suggestion: dynamic proxies; LinFu.

I've already looked into this long and hard, and for relatively complex reasons I cannot use it. It would be too long to explain, but rest assured: if I could use it, I would. It's much, much neater than my current solution.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

凉薄对峙 2024-09-06 09:42:41

使用表达式,例如 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);
    }

Use an Expression, e.g. Write(x =>semiNice.y, 0)

This technique is used often as a way to avoid magic strings.

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);
    }
知你几分 2024-09-06 09:42:41

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

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

Have you considered using a dynamic proxy to intercept all calls to the target classes, do whatever it is you need to do, and then forward the call to the target?

Something like LinFu may do the trick: http://www.codeproject.com/KB/cs/LinFuPart1.aspx

仄言 2024-09-06 09:42:41

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

您必须按如下方式使用表达式树,

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


}

Following code suports Refractoring, No additional code to write, however it may not execute very fast because of reflection but, it certainly will give you access to what you want.

You have to play with Expression tree a little as follow,

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


}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文