Показ дописів із міткою c#. Показати всі дописи
Показ дописів із міткою c#. Показати всі дописи

неділя, 28 серпня 2011 р.

Mono.Cecil vs SystemReflection

Бесспорно, Mono.Cecil круче навороченее и всякое такое.
Но если небоходимо сравнить 2 метода Cecil не представляет быстрого способа это сделать.
Сесил не сохраняет информации о том, что реализация метода это просто набор байт. Есть возможность посмотреть на высокоровневые штуки под названием Opcode. Можно посмотреть на параметры опкода. Но получить набор байт представляющих метод нереально.
Приходится  делать так

var bytes = string.Join(",", method.Body.Instructions);

С другой стороны, System.Reflection.MethodInfo позволяет сделать GetILAsByteArray.
Но с ним не будет работать Cecil.Decompiler.

З.Ы. Понятное дело что исходники Mono.Cecil открыты и можно отправить туда патч. Но примут его или нет - вопрос, а плодить сущности без необходимости не хочется.

четвер, 25 серпня 2011 р.

AsmDiff unit testing

Пытаюсь добавить юнит тесты к проекту.   И обнаруживаю что не все так просто.

Тот же самый ParameterDefinition из Mono.Cecil объявлен как sealed. Это значит, что например Moq не сможет с ним работать. Придется смотреть на RhinoMock  и товарищей. Хотя что-то мне подсказывает что они тоже обломаются.
Upd. RhinoMock тоже не сумел.

Сюрпризы на этом не заканчиваются.

Например, Mono.Cecil теряет информацию о generic параметрах метода в FullString. В качестве примера


// three genrics, parameter uses generic type, return type int
        int ThreeGenericsInt<T, S, Q>(Dictionary<S, Q> dic)
превращается в 

System.Int32 CoolLibrary.Test::ThreeGenericsInt(System.Collections.Generic.Dictionary`2<S,Q>)


Если убрать generic параметр T, 

// two genrics, parameter uses generic type, return type int
        int ThreeGenericsInt<S, Q>(Dictionary<S, Q> dic)
на выходе будет то же самое

System.Int32 CoolLibrary.Test::ThreeGenericsInt(System.Collections.Generic.Dictionary`2<S,Q>)
И удобный способ заполнения Dictionary на основе method.FullName идет лесом. Конечно вопрос решаемый, но подобных мелочей выскакивает слишком уж много.

Например, считать 2 следующих определения изменением одного метода, или удалением первого, и добавлением нового?

        int ThreeGenericsInt<T, S, Q>(Dictionary<T, Dictionary<S, Q>> dic)
        int ThreeGenericsInt<T, S, Q>(Dictionary<T, Dictionary<S, Q>> dic) where T: IEnumerable
А ведь еще есть атрибуты, изменение котрых тоже стоит отслеживать, поля и свойства и всякое такое прочее.

пʼятниця, 19 серпня 2011 р.

AsmDiff вер0.1

Известный в узких кругах Eugene Kirpichov написал в гугллоплюсе


Project to do when at the airport: .NET assembly diff tool. Read two folders of assemblies, report what's different. Should take a few hours and be very useful from the start - surprisingly there's no free tool to do that. I need to know what's really different (e.g. which methods have different code) and I'm not interested in a differing build timestamp or GUID.


У меня выдались свободных полчаса и тулзу я сделал.


На c# c Linq оно решается в один метод.

Code Snippet
  1. public static IEnumerable<DiffRecord> CecilAssebmlyDiff(AssemblyDefinition firstAssembly, AssemblyDefinition secondAssembly)
  2. {
  3.     var firstData = from module in firstAssembly.Modules
  4.                     from type in module.GetTypes()
  5.                     from method in type.Methods
  6.                     let parameters = method.Parameters
  7.                     let returnType = method.ReturnType
  8.  
  9.                     // for some system methods body can be null, so we should check it
  10.                     where method.HasBody
  11.                     let instructions = method.Body.Instructions
  12.  
  13.                     let code = method.SourceCode()
  14.                     let bytes = code
  15.                     select new { type, method, parameters, returnType, bytes };
  16.  
  17.     var secondData = from module in secondAssembly.Modules
  18.                      from type in module.GetTypes()
  19.                      from method in type.Methods
  20.                      let parameters = method.Parameters
  21.                      let returnType = method.ReturnType
  22.  
  23.                      // for some system methods body can be null, so we should check it
  24.                      where method.HasBody
  25.                      let instructions = method.Body.Instructions
  26.  
  27.                      let code = method.SourceCode()
  28.                      let bytes = code
  29.                      select new { type, method, parameters, returnType, bytes };
  30.  
  31.     var changedMethods = from firstMethod in firstData
  32.                          from secondMethod in secondData
  33.                          where
  34.                              // type names are the same
  35.                          firstMethod.type.FullName.Equals(secondMethod.type.FullName)
  36.                              // method names are the same
  37.                           && firstMethod.method.Name == secondMethod.method.Name
  38.                              // return typse are the same
  39.                           && firstMethod.returnType.FullName == secondMethod.returnType.FullName
  40.                              // parameters are the same
  41.                           && firstMethod.parameters.Except(secondMethod.parameters, x => x.ParameterType.FullName).Count() == 0
  42.                              //!!! dirty hack, byte arrays compared by their string representations
  43.                              // bodies ARE NOT the same
  44.                          && !firstMethod.bytes.Equals(secondMethod.bytes)
  45.  
  46.                          select new { firstMethod = firstMethod, secondMethod = secondMethod };


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

Результат работы выглядит следующим образом:
$ asmdiff first second
CoolLibrary.dllCoolLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=nullInt32 Test.Sample(x:String)
private int Sample(string x){return 66;}

CoolLibrary.dllCoolLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=nullInt32 Test.Sample(x:String)
private int Sample(string x){return 33;}

Архив с кодом на файлопомойке
В ближайшее время залью на какой-нибудь github.

Скачать сорцы и примеры можно здесь : проект на гитхабе

использовать просто "asmdiff folder1 folder2"

Зы. Рекурсивного обхода директорий нет.
ЗЫЫ. Обработки исключений тоже пока никакой.