Показаны сообщения с ярлыком C#. Показать все сообщения
Показаны сообщения с ярлыком C#. Показать все сообщения

воскресенье, 20 декабря 2020 г.

Сравнение флотов (чисел с плавающей точкой) в NUnit

0.9999999999
vs
1.0000000001
У чисел с плавающей запятой (float, double) есть такая особенность, что из-за проблем потери точности, они могут получаться не равными, даже если должны быть такими с точки зрения математики. И это зависит не только от используемых операций, но и от архитектуры железа, а также флагов процессора, установленных в момент вычислений.
Например, 72/6 может быть не равно 3*4.
Но сравнивать их все равно нужно. Поэтому используется сравнение с определенной точностью. Берется модуль (абсолютное значение) разности этих чисел и сравнивается с небольшой константой. Если модуль разницы больше этой константы, числа считаются разными.
В библиотеке NUnit для таких сравнений есть специальные конструкции
Вместо велосипедов
Assert.IsTrue(Mathf.Abs(actualValue - expectedValue) < tolerance);
и
Assert.Less(Mathf.Abs(actualValue - expectedValue), tolerance);
В NUnit есть
Assert.AreEqual(expectedValue, actualValue, tolerance); 

понедельник, 1 июня 2020 г.

Совмещение логирования и сбора метрик

Как сосчитать количество ошибок и предупреждений в сервисе?
В обертку над логгером встраиваем счетчики, собирающие телеметрию.

class Telemetry
{
	public int Warnings;
	public int Errors;
}

static class Log
{
	public static Telemetry Telemetry = new Telemetry();

	public static void Warning(string msg)
	{
		Telemetry.Warnings++;
		Console.WriteLine($"[Warning] {msg}");
} public static void Error(string msg) { Telemetry.Errors++; Console.WriteLine($"[Error] {msg}"); } }

вторник, 23 октября 2018 г.

Просто взять и сжать массив байтов в C#




К сожалению, .NET имеет API только для сжатия и распаковки потоков данных. А если нужно сжать просто массив байтов в другой массив байтов, приходится использовать внешние библиотеки. Или написать вспомогательный класс, например, такой:

using System.IO;
using System.IO.Compression;
 
namespace Compression
{
    public static class Zip
    {
        public static byte[] Compress(byte[] src)
        {
            using (var input = new MemoryStream(src))
            {
                using (var output = new MemoryStream())
                {
                    using (var compressor = new GZipStream(output, CompressionMode.Compress))
                    {
                        input.CopyTo(compressor);
                    }
                    return output.ToArray();
                }
            }
        }
 
        public static byte[] Decompress(byte[] src)
        {
            using (var input = new MemoryStream(src))
            {
                using (var decompressor = new GZipStream(input, CompressionMode.Decompress))
                {
                    using (var output = new MemoryStream())
                    {
                        decompressor.CopyTo(output);
 
                        return output.ToArray();
                    }
                }
            }
        }
    }
}
 

суббота, 17 февраля 2018 г.

Потокобезопасный Singleton на C#

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

public class Singleton
{
    private static readonly object syncObj = new object();
    private static volatile Singleton instance;

    public Singleton Shared()
    {
        if (instance == null)
        {
            lock (syncObj)
            {
                if (instance == null)
                {
                    instance = new Singleton();
                }
            }
        }

        return instance;
    }

    private Singleton()
    {            
    }
    
    // ...
}

Двойная проверка на null нужна для того, чтобы не делать блокировок без необходимости. А если хочется совсем без блокировок? Изучая исходный код пула массивов от майкрософт, нашел реализацию lockfree синглтона. Вот она, очищенная от лишних деталей самого пула:

public class Singleton
{
    private static Singleton instance= null;

    public static Singleton Shared
    {
        get { return Volatile.Read(ref instance) ?? EnsureSharedCreated(); }
    }

    private static Singleton EnsureSharedCreated()
    {
        Interlocked.CompareExchange(ref instance, Create(), null);
        return instance;
    }

    public static Singleton Create()
    {
        return new Singleton();
    }
    
    private Singleton()
    {
    }
    
    // ...
}

пятница, 19 мая 2017 г.

Метод Main() в C#

Метод Main служит для задания точки входа в программу.
Visual Studio по умолчанию создает метод Main следующего вида:
class Program
{
    static void Main(string[] args)
    {    
    }
}

Расположение

Метод Main может быть расположен в любом классе программы. Но он должен быть только один. Если их будет несколько, компилятор выдаст ошибку:
error CS0017: Program has more than one entry point defined. Compile with /main to specify the type that contains the entry point.
То есть в такой ситуации нужно будет явно указывать метод, который будет являться точкой входа.

Область видимости

Метод является private, хотя явно это и не указано. Делается это для того, чтобы случайно не вызвать его из другого места. Хотя можно объявить его и public, все продолжит работать.

Возвращаемое значение

По-умолчанию метод Main не возвращает значения, но он может быть объявлен с возвращаемым значением типа int.
class Program
{
  static int Main(string[] args)
  {
    return 0;
  }
}

Почему static?

Метод Main должен быть статическим для того, чтобы его вызов был возможен без создания экземпляра класса, в котором он объявлен.

Аргументы

Метод Main принимает массив строк. В нем содержатся аргументы, переданные программе при запуске. Если ваша программа не обрабатывает переданные ей аргументы, можно их опустить:
class Program
{
  static int Main()
  {
    return 0;
  }
}

Самый короткий вариант не содержит аргументов, и не возвращает значения:
class Program
{
  static void Main()
  {
  }
}

четверг, 10 октября 2013 г.

Бесконечная рекурсия сводит Microsoft unit testing framework с ума

Совершенно случайно наткнулся на странное поведение юнит-тестов Microsoft. Нажимаю запуск всех тестов, проходит несколько секунд... И возвращаюсь в исходное состояние. Тестов как будто и не запускалось...Никаких ошибок, исключений, предупреждений, ничего... Некоторые тесты по отдельности запускались и успешно отрабатывали, а некоторые нет...
При отладке одного из тестов, вводящих Visual Studio в ступор, я увидел-таки заветную ошибку "An unhandled exception of type 'System.StackOverflowException' occurred". Получается, что при ошибке переполнения стека (например, из-за бесконечной рекурсии) простой запуск юнит-тестов не возвращает ошибку, а исключение "проглатывается" системой запуска тестов...
Например, такой код приведет к подобному поведению:
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace RecursionUnitTest
{
    public class Recurse
    {
        public int Run()
        {
            return Run();
        }
    }

    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestRecursion()
        {
            Recurse rec = new Recurse();

            Assert.AreEqual(0, rec.Run());
        }
    }
}

пятница, 20 сентября 2013 г.

Сравнение коллекций/массивов в юнит-тестах Microsoft

Если вы используете фреймворк для юнит тестов, встроенный в Visual Studio, сравнение полученного результата с ожидаемым обычно производите используя класс Assert.
Но, если попробовать сравнить значения в коллекции или массиве (например, int[]) Assert.AreEqual() будет возвращать false для разных экземпляров массивов, даже если содержимое массивов одинаково.

Например:
[TestMethod]
public void TestArraysEquality()
{
  int[] array1 = new int[] { 1, 2, 3 };

  Assert.AreEqual(new int[] { 1, 2, 3 }, array1);
}

В результате тест завершается с ошибкой Assert.AreEqual failed.
Причина в том, что с массивом Assert.AreEqual проверяет равенство ссылок. Для простого примера, наподобие приведенного, можно просто написать три ассерта, по одному на каждый элемент... Но это решение на любителя. Или можно воспользоваться классом CollectionAssert, который пройдет через весь массив и проверит каждый элемент на равенство.
[TestMethod]
public void TestArraysEquality()
{
  int[] array1 = new int[] { 1, 2, 3 };

  CollectionAssert.AreEqual(new int[] { 1, 2, 3 }, array1);
}

Теперь тест завершится успешно.
К сожалению. CollectionAssert не обходит внутренние коллекции, и, если сравнить коллекцию коллекций, например, List<<int>>, то CollectionAssert.AreEqual все равно завершится ошибкой.