§1.Обзор Ides
Ides — это неизменяемая структура данных (readonly struct) размером 16 байт (128 бит),
созданная для экосистемы НевелГрад. Она объединяет алгоритмы генерации UUID v4 и ULID,
обеспечивая лексикографическую сортируемость (в режиме ULID) и строковое представление в Base32 с использованием кириллицы.
Технические характеристики
- Размер: 16 байт (128 бит)
- Генерация: UUID v4 (122 бита энтропии) или ULID (48 бит времени + 80 бит энтропии)
- Кодировка: Base32 (кириллица + арабские цифры, 26 символов)
- ГПСЧ: Xoshiro256** (период 2^256 - 1), сидирование через
RandomNumberGenerator - Потокобезопасность: Lock-free генерация за счет
[ThreadStatic]состояний - Интеграция: EF Core (ValueConverters), System.Text.Json, TypeConverter
§2.Структура данных
Ides представляет собой readonly struct с явным размещением полей (StructLayout.Explicit) для прямого доступа к памяти:
[StructLayout(LayoutKind.Explicit, Size = 16)]public readonly partial struct Ides : IEquatable<Ides>, IComparable<Ides>{ [FieldOffset(0)] readonly byte b0; [FieldOffset(1)] readonly byte b1; // ... поля b2-b14 ... [FieldOffset(15)] readonly byte b15;}Форматы представления
Ides может быть представлен в нескольких форматах:
byte[]
16 байт
0x1A 0x2B ...
Бинарное представлениеstring
26 символов
01АБВГДЕЖЗИКЛМНПРСТУФХЦЧ
Base32 (кириллица)§3.Генерация идентификаторов
ULID режим (время + случайность)
В режиме ULID первые 6 байт содержат timestamp (миллисекунды с Unix epoch), а оставшиеся 10 байт — случайные данные. Это обеспечивает сортируемость идентификаторов по времени создания.
// Генерация Ides в формате ULIDIdes id = Ides.NewIdes();// Результат: 26 символов Base32 на кириллицеConsole.WriteLine(id.ToString());// Вывод: "07ЗЖ4РСТ01АБВГДЕЖЗИКЛМНПРСТУ"// Получение времени созданияDateTimeOffset? created = id.Time;Console.WriteLine($"Создан: {created:yyyy-MM-dd HH:mm:ss}");Идентификаторы в режиме ULID лексикографически сортируются по времени создания. Это позволяет использовать их в качестве кластеризованных первичных ключей (Clustered Primary Keys) в реляционных базах данных, минимизируя фрагментацию индексов.
UUID v4 режим (полная случайность)
В режиме UUID v4 все 16 байт генерируются случайно, с установкой битов версии (4) и варианта (10xx). Этот формат обеспечивает 122 бита энтропии и не содержит временных меток.
// Генерация Ides в формате UUID v4Ides id = Ides.NewIdesV4();// Результат: 26 символов Base32 на кириллицеConsole.WriteLine(id.ToString());// Вывод: "АБВГДЕЖЗИКЛМНПРСТУФХЦЧ01АБВГ"// Время создания будет случайным (не имеет смысла для UUID v4)DateTimeOffset? time = id.Time; // Случайное значение§4.Алфавит Base32 (кириллица)
Ides использует строгую кириллическую кодировку Base32 без латинских символов. Алфавит состоит из 32 символов: 10 арабских цифр и 22 буквы кириллицы.
Цифры (0-9)
Буквы (А-Ч)
Алфавит Base32 строго ограничен кириллицей и цифрами.
Методы Parse и TryParse вернут ошибку при наличии латинских символов (A-Z, a-z)
или любых других знаков, не входящих в заданный алфавит.
§5.Примеры использования
Создание идентификаторов
// Генерация нового ULIDIdes ulid = Ides.NewIdes();// Генерация нового UUID v4Ides uuid = Ides.NewIdesV4();// Создание из байтового массиваbyte[] bytes = new byte[16] { 0x01, 0x23, 0x45, /* ... */ };Ides fromBytes = new Ides(bytes);// Парсинг из строкиIdes parsed = Ides.Parse("07ЗЖ4РСТ01АБВГДЕЖЗИКЛМНПРСТУ");// Безопасный парсингif (Ides.TryParse("07ЗЖ4РСТ01АБВГДЕЖЗИКЛМНПРСТУ", out Ides safe)){ Console.WriteLine($"Успешно: {safe}");}Конвертация между форматами
Ides id = Ides.NewIdes();// В строку (Base32 кириллица)string base32 = id.ToString();// В байтовый массивbyte[] bytes = id.ToByteArray();// В Base64string base64 = id.ToBase64();// Неявное преобразованиеstring str = id; // Ides -> stringIdes fromStr = "07ЗЖ..."; // string -> Idesbyte[] arr = id; // Ides -> byte[]Сравнение и сортировка
Ides id1 = Ides.NewIdes();await Task.Delay(10); // Ждем 10мсIdes id2 = Ides.NewIdes();// Сравнениеbool equal = id1 == id2; // falsebool notEqual = id1 != id2; // trueint cmp = id1.CompareTo(id2); // -1 (id1 < id2)// Сортировка списка (ULID сортируются по времени)List<Ides> list = new() { id2, id1, Ides.NewIdes() };list.Sort(); // Автоматическая сортировка// Хеш-код для словарейint hash = id1.GetHashCode();Dictionary<Ides, string> dict = new();dict[id1] = "Значение";§6.Интеграция с Entity Framework Core
Ides интегрируется с EF Core через value converters и comparers.
Поддерживаются как обычные, так и nullable типы Ides?.
Настройка модели
public class MyDbContext : DbContext{ public DbSet<User> Users { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { // Автоматическая настройка всех свойств типа Ides modelBuilder.UseIdes(); // Для ASP.NET Core Identity modelBuilder.UseIdesForIdentity<ApplicationUser>(); base.OnModelCreating(modelBuilder); }}public class User{ public Ides Id { get; set; } = Ides.NewIdes(); public Ides? ParentId { get; set; } public string Name { get; set; }}
Метод UseIdes() автоматически настраивает все свойства типа Ides и Ides?
в модели, устанавливая тип колонки binary(16) и регистрируя конвертеры.
Миграции базы данных
// Создание таблицы с IdesmigrationBuilder.CreateTable( name: "Users", columns: table => new { Id = table.Column<byte[]>(type: "binary(16)", nullable: false), ParentId = table.Column<byte[]>(type: "binary(16)", nullable: true), Name = table.Column<string>(nullable: false) }, constraints: table => { table.PrimaryKey("PK_Users", x => x.Id); });§7.JSON сериализация
Ides автоматически сериализуется в JSON как строка Base32 (26 символов кириллицы). Для работы требуется регистрация конвертера.
// Регистрация конвертераvar options = new JsonSerializerOptions{ Converters = { new IdesJsonConverter() }};// СериализацияIdes id = Ides.NewIdes();string json = JsonSerializer.Serialize(id, options);// Результат: "07ЗЖ4РСТ01АБВГДЕЖЗИКЛМНПРСТУ"// ДесериализацияIdes parsed = JsonSerializer.Deserialize<Ides>(json, options);// В составе объектаvar user = new { Id = Ides.NewIdes(), Name = "Иван" };string userJson = JsonSerializer.Serialize(user, options);// {"Id":"07ЗЖ4РСТ01АБВГДЕЖЗИКЛМНПРСТУ","Name":"Иван"}§8.Полный API
Статические методы
Генерирует новый Ides в формате ULID (время + случайность).
Генерирует новый Ides в формате UUID v4 (полная случайность).
Парсит строку Base32 (26 символов) в Ides. Выбрасывает исключение при ошибке.
Безопасный парсинг строки Base32. Возвращает false при ошибке.
Возвращает первое непустое значение или Ides.Empty.
Свойства экземпляра
Представление Ides как 16 байт (только чтение).
Время создания (имеет смысл только для ULID режима).
Методы экземпляра
Возвращает копию байтового представления (16 байт).
Записывает байты в предоставленный буфер без выделения памяти.
Возвращает представление в формате Base64.
Сравнивает текущий Ides с другим (оптимизировано через SIMD).
Сравнивает два Ides для сортировки (оптимизировано через SIMD).
§9.Производительность
Замеры производительности выполнены с помощью BenchmarkDotNet (.NET 10.0) на процессоре Intel Core i7-13850HX.
Сравнение проводится с системным System.Guid. Значения указаны для среднего времени выполнения (Mean) и объема выделенной памяти (Allocated).
Генерация (Generation)
Форматирование (Formatting)
Парсинг (Parsing)
Сравнение и сериализация
§10.Сравнение с аналогами
§11.Практические примеры
Первичный ключ в базе данных
public class Article{ public Ides Id { get; set; } = Ides.NewIdes(); public string Title { get; set; } public string Content { get; set; } public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;}// Использование в репозиторииpublic class ArticleRepository{ public async Task<Article> CreateAsync(string title, string content) { var article = new Article { Id = Ides.NewIdes(), // Автоматическая генерация Title = title, Content = content }; _context.Articles.Add(article); await _context.SaveChangesAsync(); return article; }}Использование в API
[ApiController][Route("api/[controller]")]public class UsersController : ControllerBase{ [HttpGet("{id}")] public async Task<ActionResult<UserDto>> GetUser(Ides id) { // Ides автоматически парсится из URL var user = await _userService.GetByIdAsync(id); if (user == null) return NotFound(); return Ok(new UserDto { Id = user.Id, // Сериализуется как Base32 кириллица Name = user.Name }); } [HttpPost] public async Task<ActionResult<UserDto>> CreateUser(CreateUserRequest request) { var user = new User { Id = Ides.NewIdes(), // Генерация на сервере Name = request.Name, Email = request.Email }; await _userService.CreateAsync(user); return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user); }}§12.Часто задаваемые вопросы
❓ Почему Ides не использует стандартный Guid?
Стандартный System.Guid жестко привязан к спецификации RFC 4122/9562 и использует латинский алфавит для строкового представления.
Ides реализует собственную структуру для поддержки кириллического Base32 и гибридной генерации (ULID/UUID v4) в рамках стандартов НевелГрад.
❓ Можно ли конвертировать Ides в Guid и обратно?
Прямые операторы приведения между Ides и System.Guid отсутствуют.
Конвертация возможна через байтовое представление: new Guid(ides.ToByteArray()) и new Ides(guid.ToByteArray()).
❓ Какой режим выбрать: ULID или UUID v4?
Используйте ULID (NewIdes()), если требуется лексикографическая сортировка по времени создания (например, для кластеризованных индексов в БД).
Используйте UUID v4 (NewIdesV4()), если требуется максимальная энтропия и отсутствие временных меток (например, для токенов, сессий или идентификаторов корреляции).
❓ Безопасен ли генератор случайных чисел?
Генератор Xoshiro256** инициализируется (сидируется) через криптографически стойкий RandomNumberGenerator.
Сам по себе Xoshiro256** не является криптографически стойким (CSPRNG), поэтому для генерации секретов, токенов доступа или ключей шифрования следует использовать RandomNumberGenerator напрямую.
❓ Поддерживается ли работа в многопоточной среде?
Да. Состояние ГПСЧ Xoshiro256 хранится в [ThreadStatic] полях.
Каждый поток использует собственный изолированный экземпляр генератора, что исключает конкуренцию за ресурсы (lock-free) и гарантирует потокобезопасность.