Посты про .NET

LegacyJIT-x86 и первый вызов метода



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

[Benchmark]
public string Sum()
{
    double a = 1, b = 1;
    var sw = new Stopwatch();
    for (int i = 0; i < 10001; i++)
        a = a + b;
    return string.Format("{0}{1}", a, sw.ElapsedMilliseconds);
}

Интересный факт: если вы вызовете Stopwatch.GetTimestamp() перед первым вызовом метода Sum, то это увеличит скорость работы метода в несколько раз (фокус работает только для LegacyJIT-x86).

Читать дальше    Комментарии


Visual Studio и ProjectTypeGuids.cs



Это история о том, как я несколько часов пытался открыть проект в Visual Studio. Как-то раз я решил немножко поработать: стянул себе последние коммиты из репозитория, открыл Visual Studio и собрался программировать. Увы, один из моих проектов не открылся, а в окошке Output я увидел странное сообщение:

error  : The operation could not be completed.

В Solution Explorer, рядом с названием проекта была надпись "load failed", а вместо файлов было написано следующее: "The project requires user input. Reload the project for more information." Хмм, ну ок, я попробовал перегрузить проект. Увы, не помогло, я получил ещё два уже знакомых сообщения об ошибке:

error  : The operation could not be completed.
error  : The operation could not be completed.

Читать дальше    Комментарии


Blittable-типы



Вопрос дня: что выведет нижеприведённый код?

[StructLayout(LayoutKind.Explicit)]
public struct UInt128
{
    [FieldOffset(0)]
    public ulong Value1;
    [FieldOffset(8)]
    public ulong Value2;
}
[StructLayout(LayoutKind.Sequential)]
public struct MyStruct
{
    public UInt128 UInt128;
    public char Char;
}
class Program
{
    public static unsafe void Main()
    {
        var myStruct = new MyStruct();
        var baseAddress = (int)&myStruct;
        var uInt128Adress = (int)&myStruct.UInt128;
        Console.WriteLine(uInt128Adress - baseAddress);
        Console.WriteLine(Marshal.OffsetOf(typeof(MyStruct), "UInt128"));
    }
}

Если вы подумали, что в консоли напечатается два нуля (или просто два одинаковых значения), то вам нужно узнать больше про внутреннее устройство структур в .NET. Ниже представлены результаты выполнения кода в зависимости от рантайма:

MS.NET-x86MS.NET-x64Mono
uInt128Adress - baseAddress 480
Marshal.OffsetOf(typeof(MyStruct), "UInt128")000

Чтобы разобраться с ситуацией, нам необходимо узнать больше про blittable-типы.

Читать дальше    Комментарии


RyuJIT RC и свёртка констант



Update: Нижеприведённый материал справедлив для релизной версии RyuJIT (часть .NET Framework 4.6).

Задачка дня: какой из методов быстрее?

public double Sqrt13()
{
    return Math.Sqrt(1) + Math.Sqrt(2) + Math.Sqrt(3) + Math.Sqrt(4) + Math.Sqrt(5) + 
           Math.Sqrt(6) + Math.Sqrt(7) + Math.Sqrt(8) + Math.Sqrt(9) + Math.Sqrt(10) + 
           Math.Sqrt(11) + Math.Sqrt(12) + Math.Sqrt(13);
}
public double Sqrt14()
{
    return Math.Sqrt(1) + Math.Sqrt(2) + Math.Sqrt(3) + Math.Sqrt(4) + Math.Sqrt(5) + 
           Math.Sqrt(6) + Math.Sqrt(7) + Math.Sqrt(8) + Math.Sqrt(9) + Math.Sqrt(10) + 
           Math.Sqrt(11) + Math.Sqrt(12) + Math.Sqrt(13) + Math.Sqrt(14);
}

Я померил скорость работы с помощью BenchmarkDotNet для RyuJIT RC (часть .NET Framework 4.6 RC) получил следующие результаты:

// BenchmarkDotNet=v0.7.4.0
// OS=Microsoft Windows NT 6.2.9200.0
// Processor=Intel(R) Core(TM) i7-4702MQ CPU @ 2.20GHz, ProcessorCount=8
// CLR=MS.NET 4.0.30319.0, Arch=64-bit  [RyuJIT]
Common:  Type=Math_DoubleSqrtAvx  Mode=Throughput  Platform=X64  Jit=RyuJit  .NET=Current  

 Method |  AvrTime |    StdDev |         op/s |
------- |--------- |---------- |------------- |
 Sqrt13 | 55.40 ns |  0.571 ns |  18050993.06 |
 Sqrt14 |  1.43 ns | 0.0224 ns | 697125029.18 |

Как же так? Добавление в выражение одно дополнительного Math.Sqrt ускорило метод в 40 раз! Давайте разберёмся.

Читать дальше    Комментарии


Размотка маленьких циклов в разных версиях JIT



Вопрос дня: что выведет нижеприведённый код?

struct Point
{
    public int X;
    public int Y;
}
static void Print(Point p)
{
    Console.WriteLine(p.X + " " + p.Y);
}
static void Main()
{
    var p = new Point();
    for (p.X = 0; p.X < 2; p.X++)
        Print(p);
}

Правильный ответ: зависит. В JIT-x86 под CLR2 был баг, который портил эту замечательную программу. А проблема кроется в оптимизации, которая назвается раскрутка маленького цикла. Тема интересная, давайте обсудим её подробно.

Читать дальше    Комментарии


RyuJIT CTP5 и размотка циклов



Уже скоро нам будет доступен RyuJIT, JIT-компилятор следующего поколения для .NET-приложений. Microsoft любит рассказывать нам о преимуществах использования SIMD и сокращением времени JIT-компиляции. Но что можно сказать о базовых оптимизациях кода, за которые обычно отвечает компилятор? Сегодня мы поговорим о такой оптимизации как размотка (раскрутка) цикла. Если кратко, то это оптимизации кода вида

for (int i = 0; i < 1024; i++)
    Foo(i);

превращается в

for (int i = 0; i < 1024; i += 4)
{
    Foo(i);
    Foo(i + 1);
    Foo(i + 2);
    Foo(i + 3);
}

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

Читать дальше    Комментарии


Определение версии JIT в рантайме



Иногда мне в моих маленьких C#-экспериментах нужно определять версию используемого JIT-компилятора. Понятно, что её можно определить заранее исходя из окружения. Но порой мне хочется знать её в рантайме, чтобы выполнять специфичный код для текущего JIT-компилятора. Строго говоря, я хочу получать значение из следующего перечисления:

public enum JitVersion
{
    Mono, MsX86, MsX64, RyuJit
}

Я могу легко определить, что работаю под Mono, по наличию класса Mono.Runtime. Если это не так, то можно считать, что мы работаем с JIT от Microsoft. JIT-x86 легко узнать с помощью IntPtr.Size == 4. А вот чтобы отличить старый JIT-x64 от нового RyuJIT необходимо немного призадуматься. Далее я покажу, как это можно сделать с помощью бага JIT-x64, который я описывал в предыдущем посте.

Читать дальше    Комментарии


История про баг в JIT-x64



Можете ли вы сказать, что выведет следующий код для step=1?

public void Foo(int step)
{
    for (int i = 0; i < step; i++)
    {
        bar = i + 10;
        for (int j = 0; j < 2 * step; j += step)
            Console.WriteLine(j + 10);
    }
}

Если вы назвали конкретные числа, то ошиблись. Правильный ответ: зависит. Заголовок подсказывает нам, что под x64 программа может вести себя не так, как мы от неё ожидаем.

Читать дальше    Комментарии


История про инлайнинг под JIT-x86 и starg



Порой можно узнать много интересного во время чтения исходников .NET. Взглянем на конструктор типа Decimal из .NET Reference Source (mscorlib/system/decimal.cs,158):

// Constructs a Decimal from an integer value.
//
public Decimal(int value) {
    //  JIT today can't inline methods that contains "starg" opcode.
    //  For more details, see DevDiv Bugs 81184: x86 JIT CQ: Removing the inline striction of "starg".
    int value_copy = value;
    if (value_copy >= 0) {
        flags = 0;
    }
    else {
        flags = SignMask;
        value_copy = -value_copy;
    }
    lo = value_copy;
    mid = 0;
    hi = 0;
}

В комментарии сказано, что если метод содержит IL-опкод starg, то он не может быть заинлайнен под x86. Любопытно, не правда ли?

Читать дальше    Комментарии


Статические поля в generic-классах



Сегодня мы кратко поговорим о статических полях в generic-классах. Тема простая, но у некоторых разработчиков она вызывает трудности. Итак, задачка: что выведет следующий код?

class Foo<T>
{
  public static int Bar;
}
void Main()
{
  Foo<int>.Bar++;
  Console.WriteLine(Foo<double>.Bar);
}

Читать дальше    Комментарии


List.ForEach в .NET 4.5



Продолжим обсуждать тему изменения коллекции внутри цикла foreach. Следующий код

var list = new List<int> { 1, 2 };
foreach (var i in list)
{
    if (i == 1)
        list.Add(3);
    Console.WriteLine(i);
}

выбросит InvalidOperationException. А как думаете, что случится при выполнении цикла через List<T>.ForEach?

var list = new List<int> { 1, 2 };
list.ForEach(i =>
{
    if (i == 1)
        list.Add(3);
    Console.WriteLine(i);
});

Правильный ответ: зависит.

Читать дальше    Комментарии


Сайд-эффект внутренней реализации List



Если вы делаете foreach по некоторому List-у, то менять итерируемый лист внутри цикла крайне не рекомендуется, ведь это верный способ получить InvalidOperationException. А теперь загадка: как думаете, что случится со следующим кодом:

var list = new List<int> { 0, 1, 2 };
foreach(var x in list)
{
  if (x == 0)
  {
    for (int i = int.MinValue; i < int.MaxValue; i++)
      list[0] = 0;
    list.Add(3);
  }
  Console.WriteLine(x);
}

Читать дальше    Комментарии


Об экранировании URI в .NET



Часть 1

Загадка на сегодня: что выведет код?

var uri = new Uri("http://localhost/%2F1");
Console.WriteLine(uri.OriginalString);
Console.WriteLine(uri.AbsoluteUri);

Правильный ответ: зависит. Давайте немножко поразбираемся. Читать дальше    Комментарии


Nullable-арифметика



Что будет, если null поделить на ноль? А сколько будет null | true? А null & true? А ((string)null + null)?

Практика подсказывает, что C#-разработчики зачастую не особо задумываются о том, как будут оцениваться выражения, если один из операндов равен null. Поэтому я решил составить небольшую шпаргалку на эту тему.

Читать дальше    Комментарии


Об UTF-8-преобразованиях в Mono



Данный пост является логическим продолжением поста Джона Скита “When is a string not a string?”. Jon showed very interesting things about behavior of ill-formed Unicode strings in .NET. I wondered about how similar examples will work on Mono. And I have got very interesting results.

Experiment 1: Compilation

Let's take the Jon's code with a small modification. We will just add text null check in DumpString:

using System;
using System.ComponentModel;
using System.Text;
using System.Linq;
[Description(Value)]
class Test
{
    const string Value = "X\ud800Y";
    static void Main()
    {
        var description = (DescriptionAttribute)typeof(Test).
            GetCustomAttributes(typeof(DescriptionAttribute), true)[0];
        DumpString("Attribute", description.Description);
        DumpString("Constant", Value);
    }
    static void DumpString(string name, string text)
    {
        Console.Write("{0}: ", name);
        if (text != null)
        {
            var utf16 = text.Select(c => ((uint) c).ToString("x4"));
            Console.WriteLine(string.Join(" ", utf16));
        }
        else
            Console.WriteLine("null");
    }
}

Читать дальше    Комментарии


Endianness в .NET



Endian funny image

Рассмотрим простую задачку: что выведет следующий код?

[StructLayout(LayoutKind.Explicit)]
struct UInt16Wrapper
{
  [FieldOffset(0)]
  public UInt16 Value;
  [FieldOffset(0)]
  public Byte Byte1;
  [FieldOffset(1)]
  public Byte Byte2;
}
void Main()
{
  var uint16 = new UInt16Wrapper();
  uint16.Value = 1 + 2 * 256;
  Console.WriteLine(uint16.Byte1);
  Console.WriteLine(uint16.Byte2);
}

Полагаю, что внимательный читатель должен обратить внимание на название поста и дать правильный ответ: «зависит». Сегодня мы немного поговорим о том, как в .NET обстоят дела с порядком байтов. Читать дальше    Комментарии


Эти занимательные региональные настройки



Сегодня мы поговорим о региональных настройках. Но сперва — небольшая задачка: что выведет нижеприведённый код? (Код приведён на языке C#, но рассматривается достаточно общая проблематика, так что вы можете представить на его месте какой-нибудь другой язык.)

Console.WriteLine((-42).ToString() == "-42");
Console.WriteLine(double.NaN.ToString() == "NaN");
Console.WriteLine(int.Parse("-42") == -42);
Console.WriteLine(1.1.ToString().Contains("?") == false);
Console.WriteLine(new DateTime(2014, 1, 1).ToString().Contains("2014"));
Console.WriteLine("i".ToUpper() == "I" || "I".ToLower() == "i");

Сколько значений true у вас получилось? Если больше 0, то вам не мешает узнать больше про региональные настройки, т. к. правильный ответ: «зависит». К сожалению, многие программисты вообще не задумываются о том, что настройки эти в различных окружениях могут отличаться. А выставлять для всего кода InvariantCulture этим программистом лениво, в результате чего их прекрасные приложения ведут себя очень странно, попадая к пользователям из других стран.

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

Читать дальше    Комментарии


Happy Monday!



Хотелось бы рассказать историю одного волшебного бага. Волшебство его заключалось в том, что он не давал нам отлаживать по понедельникам. Я сейчас совершенно серьёзно: каждый понедельник у нас отваливался Debug mode. Мало того, этот баг с циничным видом желал нам счастливого понедельничка. Для меня это был очень ценный урок в плане того, какие же всё-таки разнообразные бывают проблемы. Возможно, кому-то ещё эта история покажется любопытной.

Итак, как же я впервые встретился с этой багой. Был замечательный вечер воскресенья, ничего не предвещало беды. В понедельник планировался очередной релиз нашей программки (ничего мажорного, но клиенты ждали обещанных мелких фич). На часах отображалось 00:00, и тут мне пришла в голову мысль, что один из пользовательских сценариев для новой фичи мы не проработали. Нужно было дописать всего несколько дополнительных строк, делов минут на 10. Я решил, что сейчас быстренько допишу нужную логику и с чистой совестью лягу спать. Запускаю студию, запускаю билд проекта, жду. И тут моё лицо становится озадаченным, т.к. я вижу ошибку:

Error connecting to the pipe server.

Хм... Странная ошибка-то какая-то.

Читать дальше    Комментарии


Получение MCP: личный опыт



Не так давно мне на работе перепал ваучер на бесплатную сдачу экзамена от Microsoft. «А почему бы и нет?» — подумал я. План был выполнен успешно, в связи с чем мне хотелось бы поделиться личным опытом. Возможно, эта история пригодится тем, кто только собирается начать получать сертификации от Microsoft.

Читать дальше    Комментарии


Странное поведение FindElementsInHostCoordinates в WinRT



Есть в Silverlight отличный метод: VisualTreeHelper.FindElementsInHostCoordinates — позволяет выполнять HitTest, т.е. для некоторой точки или прямоугольника искать все объекты визуального поддерева, которые с этими точкой или прямоугольником пересекаются. Внешне точно такой же метод VisualTreeHelper.FindElementsInHostCoordinates можно встретить в WinRT. И вроде выглядит-то он точно также, но есть нюанс: работает этот чудо-метод в разных версиях платформы по-разному. Давайте разберёмся.

Читать дальше    Комментарии


Про System.Drawing.Color и оператор ==



Для многих стандартных структур в .NET-е переопределён оператор ==, который позволяет легко сравнивать ваши объекты. К сожалению, далеко не все задумываются о том, что на самом деле сравнивается при работе с этим замечательным оператором. В этой короткой заметке мы посмотрим логику сравнения объектов на примере System.Drawing.Color. Как вы думаете, что выведет следующий код:

var redName = Color.Red;
var redArgb = Color.FromArgb(255, 255, 0, 0);
Console.WriteLine(redName == redArgb);

Читать дальше    Комментарии


Настраиваем конфигурации сборок в .NET



При создании нового проекта в Visual Studio по умолчанию вы получаете две конфигурации сборки: Debug и Release. И для большинства мелких проектов этого вполне достаточно. Но с ростом проекта может возникнуть потребность добавить дополнительные конфигурации. И хорошо, если нужно добавить одну-две новые конфигурации, а если их добрый десяток? А если при этом в солюшене находится штук 20 проектов, для каждого из которых эти конфигурации нужно настроить? В данном случае управлять параметрами сборки и модифицировать их становится достаточно сложно.

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

Читать дальше    Комментарии


Cache-Conscious Binary Search



Рассмотрим простую задачу: есть некоторый достаточно большой неизменный набор чисел, к нему осуществляется множество запросов на наличие некоторого числа в этом наборе, необходимо максимально быстро эти запросы обрабатывать. Одно из классических решений заключается в формировании отсортированного массива и обработке запросов через бинарный поиск. Но можно ли добиться более высокой производительности, чем в классической реализации? В этой статье мне хотелось бы рассказать про Cache-Conscious Binary Search. В данном алгоритме предлагается переупорядочить элементы массива таким образом, чтобы использование кэша процессора происходило максимально эффективно.

Читать дальше    Комментарии


Jon Skeet's Quiz



Однажды Джона Скита попросили сформулировать три интересных вопроса на знание C#. Он спросил следующее (оригинал вопросника, перевод статьи):

  • Q1. Вызов какого конструктора можно использовать, чтобы следующий код вывел True (хотя бы в реализации Microsoft.NET)?
object x = new /* fill in code here */;
object y = new /* fill in code here */;
Console.WriteLine(x == y);

Учтите, что это просто вызов конструктора, вы не можете поменять тип переменных.

  • Q2. Как сделать так, чтобы следующий код вызывал три различных перегрузки метода?
void Foo()
{
    EvilMethod<string>();
    EvilMethod<int>();
    EvilMethod<int?>();
}
  • Q3. Как заставить следующий код выбросить исключение во второй строчке с помощью локальной переменной (без хитрого изменения её значения)?
string text = x.ToString(); // No exception
Type type = x.GetType(); // Bang!

Вопросы показались мне интересными, поэтому я решил обсудить их решения.

Читать дальше    Комментарии


Внутреннее устройство массивов в .NET



Иногда бывает полезно понимать, как выглядит внутреннее представление объектов, с которыми мы работаем. В этой статье я хотел бы поговорить о массивах: как именно они хранятся в памяти, какие IL-команды используются для работы с ними, как выглядит ассемблерный код при обращении к их элементам. Я рассмотрю три вида массивов: single (T[]), rectangular (T[,]), jagged (T[][]). Также будет затронута тема массивов с ненулевой нижней границей (T[*]) и нюансов работы с ними.

Читать дальше    Комментарии


Учимся округлять в C#



А знаете ли вы, что Math.Round(1.5) == Math.Round(2.5) == 2? Можете ли сходу сказать, сколько будет -7%3 и 7%-3? Помните ли, чем отличаются Math.Round, Math.Floor, Math.Ceiling, Math.Truncate? А как происходит округление при использовании string.Format? Давайте немного погрузимся в мир округлений и разберёмся с нюансами, которые не для всех могут быть очевидными.

Читать дальше    Комментарии


Вся правда о TypeHandle в .NET



В разных умных книжках и статьях про .NET я часто наталкивался на упоминания про TypeHandle. Чаще всего пишут, что у каждого .NET-объекта в заголовке находится некоторый TypeHandle, который представляет собой ссылку на тип. Ещё пишут, что TypeHandle — это всегда указатель на таблицу методов типа. А в некоторых местах мне доводилось встречать информацию о том, что TypeHandle указывает на некий TypeDesc. В общем, я устал от неразберихи: давайте вместе разберёмся что к чему. А для этого нам придётся немного подизассемблировать, поизучать дампы памяти и залезть в исходники CLI.

Читать дальше    Комментарии


Сравнение производительности массивов в .NET



Часть 1

Платформа .NET поддерживает два способа задания многомерных массивов: прямоугольные (rectangular) и изломанные (jagged). Второй способ по сути представляет собой массив массивов. Это обстоятельство создаёт у многих программистов иллюзию того, что jagged-массивы должны работать медленнее, т.к. обращение к их элементам реализуется через многократные переходы по ссылкам в управляемой куче. Но на самом деле jagged-массивы могут работают быстрее (если речь идёт непосредственно о работе с массивами, а не о их инициализации), ведь они представляют собой комбинацию одномерных (single) массивов, работа с которыми в CLR весьма оптимизирована (за счёт IL-команд newarr, ldelem, ldelema, ldlen, stelem). Другим подходом к представлению многомерных данных является использование одномерного массива с ручным преобразованием координат (в массиве размерности NM для обращения к элементу [i,j] будем писать [iM+j]). Если производительности не хватает, то можно использовать неуправляемый код, но этот случай мы сейчас рассматривать не будем, остановимся на трёх вышеозначенных способах. Для замеров времени используется BenchmarkDotNet. Рассмотрим C# код, который замеряет время работы каждого варианта (полный вариант кода: MultidimensionalArrayProgram.cs, тестировать следует в Release mode without debugging). Данные результаты получены в сборке под x64 для процессора Intel Core i7-3632QM CPU 2.20GHz и параметров N=M=100, IterationCount=100000. Исследование вопроса о влиянии используемой архитектуры и параметров запуска на результат бенчмарка можно найти во второй части статьи. Читать дальше    Комментарии


Об итерировании статичных массивов в .NET



Часть 1

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

В этой статье мне хотелось бы поговорить о скорости доступа к массивам, ссылки на которые хранятся в статичных переменных. Дело в том, что в скорость итерирования по ним в зависимости от условий запуска может быть ниже, чем для массива, ссылка на который хранится в обычном поле экземпляра класса или локальной переменной. Рассмотрим пример. Читать дальше    Комментарии


Недокументированные ключевые слова C# или превращаем объект в тыкву



Стандартный компилятор C# поддерживает 4 недокументированных ключевых слова: __makeref, __reftype, __refvalue, __arglist. Эти слова даже успешно распознаются в Visual Studio (хотя, ReSharper на них ругается). Они не даром исключены из стандарта — их использование может повлечь серьёзные проблемы с безопасностью. Поэтому не нужно их использовать везде подряд, но в отдельных исключительных случаях они могут пригодиться. В этом посте я обсужу предназначение недокументированных команд, рассмотрю вопросы их производительности и научусь превращать объект в тыкву.

Читать дальше    Комментарии


Неожиданное место для сборки мусора в .NET



Платформа .NET обеспечивает нас высокоинтеллектуальным сборщиком мусора, который избавляет от рутины ручного управления памятью. И в 95% случаев можно действительно забыть про память и связанные с ней нюансы. Но вот оставшиеся 5% обладают своей спецификой, связанной с неуправляемыми ресурсами, слишком большими объектами и т.д. И тут лучше бы хорошо разбираться в том, как производится сборка мусора. В противном случае вас могут ждать очень неприятные сюрпризы.

Как вы думаете, может ли GC собрать объект до того, как выполнится последний из его методов? Оказывается, может. Правда, для этого необходимо запустить приложение в Release mode и отдельно от студии (without debugging). В этом случае JIT-компилятор сделает определённые оптимизации, в результате которых такая ситуация возможна. Разумеется, делает он это только тогда, когда в оставшемся теле метода нет ссылок на сам объект или его поля. Казалось бы, достаточно невинная оптимизация. Но она может привести к проблемам, если мы имеем дело с неуправляемыми ресурсами: сборка объекта может произойти до того, как закончится операция над неуправляемым объектом, что вполне вероятно повлечёт падение приложения.

Читать дальше    Комментарии


Неочевидности в использовании C#-замыканий



Язык C# даёт нам возможность пользоваться замыканиями — мощным механизмом, который позволяет анонимным методам и лямбдам захватывать свободные переменные в своём лексическом контексте. И в .NET-мире многие программисты очень любят использовать замыкания, но немногие понимают, как они действительно работают. Начнём с простого примера:

public void Run()
{
  int e = 1;
  Foo(x => x + e);
}

Ничего сложного тут не происходит: мы просто «захватили» локальную переменную e в лямбду, которая передаётся в некоторый метод Foo. Посмотрим, во что компилятор развернёт такую конструкцию:

public void Run()
{
  DisplayClass c = new DisplayClass();
  c.e = 1;  
  Foo(c.Action);
}
private sealed class DisplayClass
{
  public int e;
  public int Action(int x)
  {
    return x + e;
  }
}

Читать дальше    Комментарии


Проблема с FPU при вызове .NET-логики из Delphi



Ситуация: мы пишем основную логику приложения на C#, но есть необходимость использовать её из Delphi. Для этих целей пользуемся COM-обёрткой, которая успешно справляется с поставленной задачей. Целевая функция перед возвращением результата показывает диалоговое WPF-окно, с которым можно сделать что-нибудь полезное. Проверяем на простом примере — всё отлично работает.

Проблема: в некоторых Delphi приложений окно выбрасывает исключение. Но исключение странное: при формировании WPF-окна падает, скажем, выставление ширины некоторого элемента. Но это только в некоторых приложениях. А в остальных — тот же самый код на тех же самых данных отлично работает.

В чём же дело?

Читать дальше    Комментарии


FastColoredTextBox — божественный RichTextBox для .NET



Появилась у меня недавно задачка сделать в WPF-приложении красивый редактор форматированного текста с определённой логикой обработки. И решил я использовать для этой задачи стандартный RichTextBox. Увы, практика показала, что этот контрол ужасно медленный. Можно было, конечно, написать свою реализацию, но это занятие долгое, а функционал нужно было прикрутить побыстрее. Первая мысль была захостить стандартный RichTextBox из WinForms. Он работает достаточно быстро, но его функционала мне не хватило. И тогда я пустился в поиск сторонних контролов. Каким же счастливым я стал, когда наткнулся на FastColoredTextBox! Изучение контрола лучше всего начать со статьи на CodeProject. Увы, NuGet-пакет автор по каким-то причинам делать не хочет, но зато есть исходники на GitHub. Итак, небольшой обзор.

Читать дальше    Комментарии


Использование изменяемых структур в свойствах классов



Названия всех классов вымышленные,
все совпадения абсолютно случайны.

Занимался я как-то раз улучшением кода проекта. И наткнулся вот на такие строчки:

public class Image
{
  public Rectangle Bounds;
}

«Ай-яй-яй! Публичное поле в классе, как же нехорошо-то! Нужно срочно превратить его в свойство!» — подумал я. И превратил:

public class Image
{
  public Rectangle Bounds { get; set; }
}

Сделал я такое невинное изменение и сразу пошёл дальше рефакторить — ведь ещё такое количество кода нуждалось в улучшении! Ну а в конце решил я запустить на всякий случай Unit-тесты. Какого же было моё удивление, когда половина тестов упала. «Да как же так! Ведь я особо-то ничего и не менял!» Ну, поехали разбираться. Читать дальше    Комментарии


Занимательное о шрифтах в .NET



Сегодня мы поговорим о замечательном классе Font. Иногда при работе с шрифтами возникают некоторые вопросы, которые не настолько очевидны, как хотелось бы. Рассмотрим несколько из них.


Q: Как опознать моноширинный шрифт?

A: Это не такой простой вопрос. Если немного погуглить, то можно найти следующий совет: описываем у себя в проекте класс LOGFONT и используем метод ToLogFont для конвертации шрифта в соответствующий объект. После этого (согласно легенде) в поле lfPitchAndFamily первый бит должен определять моноширинность шрифта. Так вот, это враньё, в современном мире поле всегда будет равно нулю. Когда-то где-то этот способ работал, но сейчас не работает. В реальности приходится использовать не очень красивое, но весьма эффективное решение типа такого:

// graphics — заранее созданный экземпляр класса Graphics
public static bool IsMonospace(Font font)
{
    return Math.Abs(graphics.MeasureString("iii", font).Width - 
                    graphics.MeasureString("WWW", font).Width) < 1e-3;
}

Читать дальше    Комментарии


Заворачиваем C#-класс в COM



Пусть у нас имеется C#-класс, который делает что-нибудь полезное, например:

public class Calculator
{
    public int Sum(int a, int b)
    {
        return a + b;
    }
}

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

Читать дальше    Комментарии