5

私は、プログラマー アプリケーションの根底にあり、特定のデータへのアクセスを検出する必要があるシステムを作成しています。私はほとんどの場合、次のようにプロパティを使用してこれを行うことができます。

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

次に、アクセスを適切に処理できるようにアクセサーgetとアクセサーを微調整します。setただし、これには、ユーザー (アプリケーション プログラマー) がすべてのデータをプロパティとして定義する必要があります。

ユーザーが (プロパティではなく) 「通常の」フィールドを持つ既存のクラスを使用したい場合、それらのアクセスを検出できません。例:

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

次のコードは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 に答える
1

動的プロキシを使用して、ターゲット クラスへのすべての呼び出しをインターセプトし、必要なことをすべて実行してから、その呼び出しをターゲットに転送することを検討しましたか?

LinFu のようなものがうまくいくかもしれません: http://www.codeproject.com/KB/cs/LinFuPart1.aspx

于 2010-04-29T17:50:41.340 に答える