Как определить, какие переменные состояния читаются / записываются в данном методе в C # - PullRequest
8 голосов
/ 14 сентября 2010

Какой самый простой способ определить, является ли данный метод чтением или записью переменной или свойства члена? Я пишу инструмент для помощи в системе RPC, в которой доступ к удаленным объектам дорог. Возможность обнаружить, что данный объект не используется в методе, может позволить нам избежать сериализации его состояния. Делать это в исходном коде вполне разумно (но иметь возможность делать это в скомпилированном коде было бы замечательно)

Я думаю, что я могу написать свой собственный простой парсер, я могу попробовать использовать один из существующих синтаксических анализаторов C # и работать с AST. Я не уверен, возможно ли это сделать с помощью сборок с помощью Reflection. Есть ли другие способы? Что было бы самым простым?

РЕДАКТИРОВАТЬ: Спасибо за все быстрые ответы. Позвольте мне дать больше информации, чтобы прояснить вопрос. Я определенно предпочитаю правильно, но это определенно не должно быть чрезвычайно сложным. Я имею в виду, что мы не можем зайти слишком далеко, проверяя крайности или невозможности (как упоминавшиеся делегаты, что было замечательно). Было бы достаточно обнаружить эти случаи и предположить, что все можно использовать, а не оптимизировать там. Я бы предположил, что эти случаи были бы относительно редкими. Идея состоит в том, чтобы этот инструмент передавался разработчикам вне нашей команды, которые не должны беспокоиться об этой оптимизации. Инструмент берет их код и генерирует прокси для нашего собственного протокола RPC. (мы используем protobuf-net только для сериализации, но не используем wcf и .net remoting). По этой причине все, что мы используем, должно быть бесплатным, иначе мы не сможем развернуть инструмент для решения вопросов лицензирования.

Ответы [ 8 ]

6 голосов
/ 15 сентября 2010

Вы можете иметь простой или правильный - что вы предпочитаете?

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

Этот простой анализ, конечно, не правильный .Если бы у вас было

class C
{
    int Length;
    void M() { int x = "".Length; }
}

Тогда вы бы неправильно сделали вывод, что M ссылается на C.Length.Это неверный результат.

правильный способ сделать это - написать полный C # -компилятор и использовать вывод его семантического анализатора, чтобы ответить на ваш вопрос.Вот как в IDE реализованы такие функции, как «перейти к определению».

2 голосов
/ 28 февраля 2011

Чтобы завершить ответ Л.Бушкина на NDepend ( Отказ от ответственности: я один из разработчиков этого инструмента ), NDepend действительно может помочь вам в этом. Код LINQ Query (CQLinq) , приведенный ниже, фактически соответствует методам, которые ...

  • не должны провоцировать вызовы RPC, но
  • , которые читают / пишутлюбые поля любых типов RPC ,
  • или которые читают / записывают любые свойства любых типов RPC ,

Обратите внимание, как сначала мы определяем 4 набора: typesRPC , fieldsRPC , propertiesRPC , methodThatShouldntUseRPC -а затем мы сопоставляем методы, которые нарушают правило.Конечно, это правило CQLinq должно быть адаптировано к вашим собственным typesRPC и methodThatShouldntUseRPC :

warnif count > 0

// First define what are types whose call are RDC
let typesRPC = Types.WithNameIn("MyRpcClass1", "MyRpcClass2")

// Define instance fields of RPC types
let fieldsRPC = typesRPC.ChildFields()
                .Where(f => !f.IsStatic).ToHashSet()

// Define instance properties getters and setters of RPC types
let propertiesRPC = typesRPC.ChildMethods()
                    .Where(m => !m.IsStatic && (m.IsPropertyGetter || m.IsPropertySetter))
                    .ToHashSet()


// Define methods that shouldn't provoke RPC calls
let methodsThatShouldntUseRPC = 
          Application.Methods.Where(m => m.NameLike("XYZ"))


// Filter method that should do any RPC call 
// but that is using any RPC fields (reading or writing) or properties
from m in methodsThatShouldntUseRPC.UsingAny(fieldsRPC).Union(
          methodsThatShouldntUseRPC.UsingAny(propertiesRPC))

let fieldsRPCUsed = m.FieldsUsed.Intersect(fieldsRPC )
let propertiesRPCUsed = m.MethodsCalled.Intersect(propertiesRPC)

select new { m, fieldsRPCUsed, propertiesRPCUsed  }
2 голосов
/ 15 сентября 2010

Прежде чем пытаться написать такую ​​логику самостоятельно, я бы проверил, можете ли вы использовать NDepend для удовлетворения ваших потребностей.

NDepend - это кодинструмент анализа зависимостей ... и многое другое.Он реализует сложный анализатор для изучения отношений между конструкциями кода и должен быть в состоянии ответить на этот вопрос.Он также работает как с исходным кодом, так и с IL, если я не ошибаюсь.

NDepend предоставляет CQL - язык запросов кода - который позволяет вам писать SQL-подобные запросы к отношениям между структурами в вашем коде.NDepend имеет некоторую поддержку сценариев и может быть интегрирован в процесс сборки.

1 голос
/ 14 сентября 2010

Моя интуиция заключается в том, что определение того, какие переменные-члены будут доступны, является неправильным подходом. Моим первым предположением о том, как это сделать, было бы просто запросить сериализованные объекты по мере необходимости (желательно в начале любой функции, которая им нужна, а не по частям). Обратите внимание, что TCP / IP (то есть алгоритм Nagle) должен объединять эти запросы, если они выполняются в быстрой последовательности и имеют небольшой размер

0 голосов
/ 15 сентября 2010

Эрик имеет право: чтобы сделать это хорошо, вам нужно то, что равносильно внешнему интерфейсу компилятора. Что он не особо подчеркнул, так это потребность в сильных возможностях анализа потока (или готовность принять очень консервативные ответы, возможно, смягченные аннотациями пользователей). Возможно, он имел в виду, что во фразе «семантический анализ», хотя его примеру «определения goto» просто нужна таблица символов, а не анализ потока.

Простой синтаксический анализатор C # может использоваться только для получения очень консервативных ответов (например, если метод A в классе C содержит идентификатор X, предположим, что он читает член класса X; если A содержит нет вызовов, то вы знаете, что он не может прочитать элемент X).

Первый шаг после этого - наличие таблицы символов компилятора и информации о типе (если метод A напрямую ссылается на член класса X, то предположим, что он читает член X; если A содержит ** no * вызовов и упоминает идентификатор X только в контексте доступа к объектам, которые не принадлежат к этому типу класса, тогда вы знаете, что он не может прочитать член X). Вы также должны беспокоиться о квалифицированных ссылках; Q.X может читать элемент X, если Q совместим с C.

Важным моментом являются вызовы, которые могут скрывать произвольные действия. Анализ, основанный только на таблицах синтаксического анализа и символов, может определить, что при наличии вызовов аргументы относятся только к константам или объектам, которые не принадлежат к классу, который A может представлять (возможно, унаследованным).

Если вы найдете аргумент с C-совместимым типом класса, теперь вы должны определить, может ли этот аргумент быть связан с this , что требует анализа и анализа потока данных:

   method A( ) {  Object q=this;
                     ...
                     ...q=that;...
                     ...
                     foo(q);
               }

foo может скрывать доступ к X. Таким образом, вам нужно две вещи: анализ потока, чтобы определить, может ли начальное присвоение q достигнуть вызова foo (это может быть нет; q = может доминировать во всех вызовах foo), и call анализ графа, чтобы определить, какие методы foo может фактически вызывать, так что вы можете проанализировать их для доступа к члену X.

Вы можете решить, как далеко вы хотите зайти, просто сделав консервативное предположение «А читает Х» в любое время, когда у вас недостаточно информации, чтобы доказать обратное. Это даст вам «безопасный» ответ (если не «правильный» или то, что я предпочел бы назвать «точным»).

Из фреймворков, которые могут быть полезны, вы можете рассмотреть Mono, который, несомненно, анализирует и создает таблицы символов. Я не знаю, какую поддержку он предоставляет для анализа потока или извлечения графа вызовов; Я не ожидал бы, что внешний интерфейсный компилятор Mono-IL сделает это, поскольку люди обычно скрывают этот механизм в JIT-части систем на основе JIT. Недостатком является то, что Mono может отставать от кривой современного C #; в прошлый раз я слышал, что он обрабатывает только C # 2.0, но моя информация может быть устаревшей.

Альтернативой является наш инструментарий реинжиниринга программного обеспечения DMS и его C # Front End . (Не продукт с открытым исходным кодом).

DMS обеспечивает общий синтаксический анализ исходного кода, построение / проверку / анализ дерева, поддержку таблиц общих символов и встроенный механизм для реализации анализа потока управления, анализа потока данных, анализа точек на точку (необходим для «Что означает объект O точка» к? "), и вызвать построение графа. Все это оборудование было протестировано с использованием внешних интерфейсов Java и C в DMS, а поддержка таблиц символов использовалась для реализации полного разрешения имен и типов в C ++, поэтому оно довольно эффективно. (Вы не хотите недооценивать работу, необходимую для создания всего этого механизма; мы работаем над DMS с 1995 года).

Интерфейс C # обеспечивает полный синтаксический анализ C # 4.0 и полное построение дерева. В настоящее время он не создает таблицы символов для C # (мы работаем над этим), и это недостаток по сравнению с Mono. Однако с такой таблицей символов у вас будет доступ ко всему механизму анализа потоков (который был протестирован с интерфейсами DMS для Java и C), и это может быть большим шагом по сравнению с Mono, если он этого не обеспечивает.

Если вы хотите сделать это хорошо, перед вами значительный объем работы. Если вы хотите придерживаться «простого», вам придется просто разобрать дерево и быть в порядке с очень консервативным.

Вы не много говорили о том, что метод написал члену. Если вы собираетесь минимизировать трафик, как вы описываете, вы хотите различать случаи «чтения», «записи» и «обновления» и оптимизировать сообщения в обоих направлениях. Анализ, очевидно, очень похож для разных случаев.

Наконец, вы можете рассмотреть возможность обработки MSIL напрямую, чтобы получить необходимую информацию; у Вас все еще будут проблемы анализа потока и консервативного анализа. Вы можете найти следующую техническую статью интересной; она описывает полностью распределенную объектную систему Java, которая должна выполнять тот же базовый анализ, что и вы, и делает это, IIRC, анализируя файлы классов и выполняя массовое переписывание байт-кода. Java Orchestra System

0 голосов
/ 14 сентября 2010

Вы можете прослушивать событие, когда свойство читается / записывается с интерфейсом, подобным INotifyPropertyChanged (хотя вы, очевидно, не будете знать, какой метод произвел чтение / запись.)

0 голосов
/ 14 сентября 2010

Под RPC вы имеете в виду .NET Remoting?Или DCOM?Или WCF?

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

0 голосов
/ 14 сентября 2010

Я думаю, что лучшее, что вы можете сделать, это явно поддерживать грязный флаг.

...