“Изящный” способ определить поле?

Я пишу систему, которая лежит в основе приложений программиста, и это должно обнаружить их доступ к определенным данным. Я могу главным образом сделать так со свойствами, как это:

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;

Где Write возвратит клон notSoNice, который является тем, что я хотел set средство доступа, чтобы сделать так или иначе. Однако использование строки довольно ужасно: если позже они осуществляют рефакторинг поле, они должны будут пробежаться через их Write("notSoNice") доступы и меняют струну.

Как мы можем определить поле? Я могу только думать о строках, ints и перечислениях (т.е. ints снова). Но:

  • Мы уже обсудили проблему со строками.
  • Ints являются болью. Они еще хуже, потому что пользователь должен помнить, которому интервал соответствует который поле. Рефакторинг является одинаково трудным.
  • Перечисления (такой как NOT_SO_NICE и Z, т.е. поля 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: динамические прокси; LinFu.

Я уже изучал это долго и трудно, и по относительно сложным причинам я не могу использовать его. Это было бы слишком длинно, чтобы объяснить, но пребывать в уверенности: если я мог бы использовать его, я был бы. Это очень, намного более опрятно, чем мое текущее решение.

5
задан Alix 30 April 2010 в 07:32
поделиться

3 ответа

Используйте выражение, например 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);
    }
2
ответ дан 15 December 2019 в 00:54
поделиться

Рассматривали ли вы использование динамического прокси для перехвата всех вызовов целевых классов, сделать все, что вам нужно, а затем перенаправить вызов цели?

Что-то вроде LinFu может помочь: http://www.codeproject.com/KB/cs/LinFuPart1.aspx

1
ответ дан 15 December 2019 в 00:54
поделиться

Следующий код поддерживает рефракторинг, нет дополнительного кода для написания, однако он может выполняться не очень быстро из-за отражения, но он, безусловно, предоставит вам доступ к тому, что вы хотите.

Вам нужно немного поиграть с деревом выражений:

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


}
1
ответ дан 15 December 2019 в 00:54
поделиться
Другие вопросы по тегам:

Похожие вопросы: