«Элегантный» способ идентификации поля? - PullRequest
5 голосов
/ 29 апреля 2010

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

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

}

Как мне получить эту информацию в Write? Я не могу найти способ извлечь эту информацию из Func<,>. Кроме того, зачем писать semiNice.Write(x => semiNice.notSoNice.y, 50); вместо semiNice.Write(() => semiNice.notSoNice.y, 50);, поскольку мы не используем x для чего-либо?

Спасибо.


РЕДАКТИРОВАНИЕ № 2: Предложение Ганса Пассанта: замена полей свойствами.

Это то, что я изначально собирался сделать, но перекомпиляция не вариант.


EDIT # 3 : предложение Бена Хоффштейна: динамические прокси; Линьф.

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

Ответы [ 3 ]

2 голосов
/ 29 апреля 2010

Используйте выражение, например, Напишите (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);
    }
1 голос
/ 30 апреля 2010

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

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

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 голос
/ 29 апреля 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...