Записи с меткой C#
Ragnarök – тёмная тема для Visual Studio
0Как-то даже странно, что я до сих пор про неё не писал, ибо пользуюсь ею уже много лет. Но вот сегодня натолкнулся на новую многообещающую тему Dark Pastel и подумал, что пора ![]()
За основу была взята одноимённая тема изначально опубликованная Томасом Рестрепо для Visual Studio 2005 и 2008. (У него есть и другие темы).
К сожалению, я ещё не встречал популярных тем, включающих в себя раскраску токенов специфичных для Resharper’а, что очень быстро сводит на нет все попытки попробовать что-то отличное от стандартных настроек. (далее…)
RavenDB, dynamic и Excel
0Интро
Уже давно хотелось пощупать главное нововведение .NET 4 – DLR, да и RavenDB выглядит очень аппетитным, а собранные вместе в одном месте дают возможность реализовать очень интересные штуки. Да и на чём потренироваться у меня есть – мой список дисков, который я веду в Excel. Давно уже хочется отказаться от таблиц и использовать что-то более гибкое.
Итак, далее последует примерный конспект моих мытарств в освоении всего нового, а именно – как работать с приложениями MS Office напрямую, основы использования динамических объектов и работа с RavenDB. На всякий случай, я использовал MS Office 2010 RTM x64, Visual Studio 2010 RTM и RavenDB build 101 (начал я с 100, но там был баг с загрузкой индексов).
Как оседлать Excel
Итак, как же нам получить все интересующие на данные из исходного файлика в формате xlsx? Побродив немного по MSDN и форумам, я заметил два основных подхода: работа как с БД через Jet OLE DB (пример на CodeProject), либо напрямую через компоненты автоматизации (COM-объекты).
Поскольку у меня много мета-информации хранится в комментариях к ячейкам, первый вариант отпадает сразу. Второй вариант тоже не сахар, ибо работа с COM – не самое благодарное занятие.
Компонентная модель
Итак, работаем с COM. Очень поверхностно, работа заключается в создании объекта нужного приложения (в моём случае – Excel), открытия интересующего нас файла и последующего взаимодействия с этим самым файлом. Самое главное – не забыть потом всё это закрыть (и нет, using здесь не помощник).
using Microsoft.Office.Interop.Excel;
<...>
private static void Main()
{
var excel = new Application();
try
{
Workbook workbook = excel.Workbooks.Open("CDDB.xlsx", ReadOnly: true, Editable: false);
Worksheet sheet = workbook.Sheets["Игры"];
foreach (Range row in sheet.Rows)
{
var objetToStore = GameConverter(row.Cells);
<...>
}
workbook.Close();
}
finally
{
excel.Quit();
}
}
Для сравнения, вот как выглядело открытие файла раньше:
Workbook workbook = excel.Workbooks.Open("CDDB.xlsx", null, true, null, null, null, null, null, null, false, null, null, null, null, null);
Достаём данные
Собственно, обработка данных. В данном примере функция GameConverter принимает одну строку-запись из файла и конвертирует её в некий объект, который будет более наглядно отражать её суть. И тут возникает вопрос, – как это сделать. Лично мне в данном случае не хотелось привязываться к какой-то конкретной объектной модели, поэтому первое, что пришло в голову – использовать анонимные объекты.
private static object GameConverter(dynamic gameRow)
{
return new
{
type = "game",
title = new
{
localized = GetField(gameRow, 1),
original = GetField(gameRow, 2),
},
publisher = GetField(gameRow, 3),
disks = GetField(gameRow, 4),
architecture = GetField(gameRow, 5),
media = GetField(gameRow, 6),
protection = GetField(gameRow, 7),
language = GetField(gameRow, 8),
shippedVersion = GetField(gameRow, 9),
latestPatch = GetField(gameRow, 10),
borrowedBy = GetField(gameRow, 11),
purchaseDate = GetField(gameRow, 12),
price = GetField(gameRow, 13),
};
}
private static object GetField(dynamic row, int idx)
{
return new { value = row[idx].Value2, comment = GetComment(row[idx].Comment) };
}
private static string GetComment(dynamic cell)
{
return cell == null ? null : cell.Shape.AlternativeText;
}
Хм… выглядит неплохо, но смущает явный копипаст. Ну да ладно, пока что сойдёт. Самое главное – уже сейчас видны преимущества использования динамических объектов, ибо я так и не разобрался, как же просто можно обратиться к полям с данными (что к чему надо кастовать на каком шаге), даже зная, как называются интересующие меня поля.
После прогона с отладчиком, видно, что объект формируется как надо:

Работа с RavenDB
Сначала, почему RavenDB? Потому что это объектное хранилище, да к тому же написанное на .NET для .NET. Идеальный вариант. Работать с ним проще простого: запускаем сервер, инициализируем клиент, подключаемся, работаем.
using Raven.Client;
using Raven.Client.Document;
using Raven.Database.Data;
<...>
private static void Main()
{
<...>
using (IDocumentStore store = new DocumentStore {Url = "http://localhost:8080/", Credentials = CredentialCache.DefaultNetworkCredentials}.Initialize())
using (IDocumentSession session = store.OpenSession())
{
Worksheet sheet = workbook.Sheets["Игры"];
foreach (Range row in sheet.Rows)
{
var o = GameConverter(row.Cells);
session.Store(o);
}
session.SaveChanges();
}
<...>
}
Вроде бы всё просто: делаем объекты, пихаем их в базу. Но что это? Падение при попытке сохранить первый же объект?

Хм… раз мы не управляем генерацией идентификаторов объектов вручную, RavenDB пытается генерировать их сам на основе имени класса сохраняемого объекта и у нашего анонимного типа оно несколько длинновато. Выхода два: делать дополнительную работу по генерации идентификаторов объектов, либо генерировать объект по-другому. Поскольку лень – двигатель прогресса, мы пойдём вторым путём.
Как очистить базу
Самым неожиданным моментом было обнаружить, что вот так просто нельзя дропнуть всю БД и начать с нового листа. Возможно, есть способ и попроще, но самый простой выход, до которого дошёл я, – это создать индекс, возвращающий все документы, а потом вызвать метод массового удаления, используя этот самый индекс.
private static void InitDb(IDocumentStore store)
{
try
{
store.DatabaseCommands.GetIndex("AllDocuments");
}
catch (InvalidOperationException)
{
store.DatabaseCommands.PutIndex("AllDocuments", new IndexDefinition {Map = "from doc in docs select new {doc}"});
}
store.DatabaseCommands.DeleteByIndex("AllDocuments", new IndexQuery {PageSize = int.MaxValue}, false);
}
Конечно, чтобы удалить все документы, придётся сначала дождаться полной индексации и, возможно, более корректно обрабатывать paging, но для прототипа сойдёт и так.
Как создавать объекты. Попытка №2
Да, анонимные объекты хороши не всегда. Следующий логичный шаг – создание объектной модели. Ничего особенно умного, просто разбить наш анонимный класс на подклассы и дать им имена.
private class Field
{
public dynamic value;
public string comment;
}
private class Title
{
public Field localized;
public Field original;
}
private class Game
{
public string type;
public Title title;
public Field publisher;
public Field disks;
public Field architecture;
public Field media;
public Field protection;
public Field language;
public Field shippedVersion;
public Field latestPatch;
public Field borrowedBy;
public Field purchaseDate;
public Field price;
}
Вот, теперь всё работает.
{
"type":"game",
"title":{
"localized":{
"value":"Fate/stay night",
"comment":null
},
"original":{
"value":"フェイト/ステイナイト",
"comment":"FEITO / SUTEI NAITO"
}
},
"publisher":{
"value":"Type-Moon",
"comment":null
},
"disks":{
"value":1,
"comment":null
},
"architecture":{
"value":"PC",
"comment":null
},
"media":{
"value":"DVD",
"comment":null
},
"protection":{
"value":"CD",
"comment":null
},
"language":{
"value":"я",
"comment":"есть патч англификации"
},
"shippedVersion":{
"value":null,
"comment":null
},
"latestPatch":{
"value":null,
"comment":null
},
"borrowedBy":{
"value":null,
"comment":null
},
"purchaseDate":{
"value":39764,
"comment":null
},
"price":{
"value":2559.05,
"comment":null
}
}
Только что-то как-то слишком много лишнего. Сплошные null’ы. Что-то мне подсказывает, что этого можно избежать, а ключ лежит в использовании DLR и конструировании объектов по мере надобности.
Как создавать объекты в runtime
Итак, ExpandoObject. Новый класс, призванный в паре с dynamic дать разработчиком большую гибкость по работе с объектами во время выполнения программы. Вещь, доступная в таких языках, как Python, Ruby, JavaScript, PowerShell и прочих. Нас же интересует добавление новых полей по мере надобности.
private static dynamic GameConverter(dynamic gameRow)
{
dynamic game = new ExpandoObject();
game.type = "game";
game.title = new ExpandoObject();
dynamic field = GetField(gameRow, 1);
if (field != null) game.title.localized = field;
field = GetField(gameRow, 2);
if (field != null) game.title.original = field;
field = GetField(gameRow, 3);
if (field != null) game.publisher = field;
field = GetField(gameRow, 4);
if (field != null) game.disks = field;
<...>
return game;
}
private static dynamic GetField(dynamic row, int idx)
{
dynamic value = row[idx].Value2;
if (value == null || value.ToString().Trim() == "") return null;
dynamic field = new ExpandoObject();
field.value = value;
dynamic comment = GetComment(row[idx].Comment);
if (comment != null) field.comment = comment;
return field;
}
private static string GetComment(dynamic cell)
{
return cell == null ? null : cell.Shape.AlternativeText;
}
Запускаем. И… это именно то, что надо
{
"type":"game",
"title":{
"localized":{
"value":"Fate/stay night"
},
"original":{
"value":"フェイト/ステイナイト",
"comment":"Подпись: FEITO / SUTEI NAITO"
}
},
"publisher":{
"value":"Type-Moon"
},
"disks":{
"value":1
},
"architecture":{
"value":"PC"
},
"media":{
"value":"DVD"
},
"protection":{
"value":"CD"
},
"language":{
"value":"я",
"comment":"Подпись: есть патч англификации для 2 из 3 путей"
},
"purchaseDate":{
"value":39764
},
"price":{
"value":2559.05
},
"Id":"expandoobjects/15"
}
Только вот смотрю я на весь этот копипаст и кажется, что можно это дело улучшить. Глянем в MSDN, ага, так и есть – ExpandoObject реализует интерфейс IDictionary. Отлично!
private static dynamic GameConverter(dynamic gameRow)
{
dynamic game = new ExpandoObject();
game.type = "game";
game.title = new ExpandoObject();
AppendFieldIfNotNull(game.title, "localized", GetField(gameRow, 1));
AppendFieldIfNotNull(game.title, "original", GetField(gameRow, 2));
var fields = new[]
{
"publisher",
"disks",
"architecture",
"media",
"protection",
"language",
"shippedVersion",
"latestPatch",
"borrowedBy",
"purchaseDate",
"price"
};
for (int i = 3; i < 14; i++)
AppendFieldIfNotNull(game, fields[i - 3], GetField(gameRow, i));
return game;
}
private static void AppendFieldIfNotNull(ExpandoObject obj, string field, dynamic value)
{
if (value == null) return;
if (value.ToString().Trim() == "") return;
// ReSharper disable RedundantCast
(obj as IDictionary<string, object>) = value;
// ReSharper restore RedundantCast
}
Здесь, кстати, нашёлся небольшой баг в решарпере (кстати, буквально за день до этого я нашёл другой баг – в компиляторе c#, который был гораздо неприятней).
Дальнейшая работа уже не так интересна – написание однотипных конвертеров для каждой страницы, перенос дополнительной мета-информации (цвет записи), правильное конвертирование данных (даты) и перенос сортировок в виде индексов, но в этом уже нет ничего нового.
Mono.SIMD на практике
2Давно хотел посмотреть, как на деле обстоят дела с потоковой обработкой данных в .NET. И вот сегодня представился случай.
Код: недописанный класс, реализующий MD5. Использовался только один оператор для первого раунда (то, что в референсной реализации называется FF()).
using System;
using CryptoLib.Hashing;
using Mono.Simd;
namespace Test
{
internal class Program
{
private static void Main()
{
var md5 = new Md5();
const uint a = 1;
const uint b = 2;
const uint c = 3;
const uint d = 4;
var s = new Vector4ui(a, b, c, d);
var x = new uint[16];
uint r1 = md5.Op1(x, a, b, c, d, 0, 12, 0);
Vector4ui r2 = md5.Op1(x, s, 0, 12, 0);
const uint cycles = 100000000;
DateTime st = DateTime.Now;
for (uint i = 0; i < cycles; i++)
r1 = md5.Op1(x, r1, b, c, d, 0, 12, 0);
DateTime e1 = DateTime.Now;
for (uint i = 0; i < cycles; i++)
r2 = md5.Op1(x, r2, 0, 12, 0);
DateTime e2 = DateTime.Now;
int bits = IntPtr.Size*8;
int ms1 = (e1 - st).Milliseconds;
int ms2 = (e2 - e1).Milliseconds;
Console.WriteLine("Running {0}-bit code.", bits);
Console.WriteLine("Normal: {0}ms", ms1);
Console.WriteLine("SIMD: {0}ms", ms2);
}
}
}
Реализация Md5.Op1() вовсе не замысловата:
public uint Op1(uint[] x,
uint a, uint b, uint c, uint d,
byte k, byte s, byte i)
{
return b + (a + F(b, c, d) + x[k] + T[i]).Rol(s);
}
public Vector4ui Op1(uint[] x, Vector4ui state, byte k, byte s, byte i)
{
state.X = state.Y + (state.X + F(state.Y, state.Z, state.W) + x[k] + T[i]).Rol(s);
return state;
}
Среда выполнения:
- 64-битная Windows 7 RC (build 7100);
- Core2 Quad Q6600 (поддержка инструкций до SSSE3);
- .NET Framework CLR v2.0.50727;
- Mono 2.4 (под Windows есть только 32-битный CLR).
Результаты по трём запускам релизного кода:
|
Normal (ms) |
SIMD (ms) |
Runtime |
Bitness |
|
331,33 |
11,33 |
Mono+ |
32 |
|
320,33 |
131,33 |
Mono- |
32 |
|
857,33 |
768,67 |
MS .NET |
32 |
|
853,67 |
774,00 |
MS .NET |
64 |
- Mono+ – с включенной оптимизацией (
-O=simd); - Mono- – с выключенной оптимизацией (
-O=-simd).
私のブログをようこそ。
0[Update] репозиторий на гугле благополучно скончался в связи с полной запущенностью проекта ^_^"
Всем заинтересованным привет.
Сразу же хочется предупредить: дальше [скорее всего] будет текст на английском и японском, так что не пугаться! (Ну, или пугаться, но не сильно ^_^). Собственно, начать бложить потихоньку меня сподвигла нужда в публичном выражении своего раздражения по поводу тех или иных косяков, которые я начал встречать в .NET (и не только) после начала над своим проектом Shogi .NET.
Тема сегодняшнего поста — очередной косяк, отрытый в .NET.
Задача: хочется выводить числа в японской системе записи (т.е. вместо 1,2,3… чтобы в строчку писалось 一、二、三…). Казалось бы, что может быть проще, ведь это национальная система записи чисел — возьмём нужную культуру и применим её при форматировании чисел!
Итак, примерный код должен выглядеть так:
var cultureInfo = new CultureInfo("ja-JP"); var str = string.Format(cultureInfo, "{0}", 100);
Console.WriteLine(str);
После запуска можно убедиться, что ожидаемого эффекта добиться не удалось.
Копаем дальше. У CultureInfo есть свойство NativeDigits, которое, как ни странно, ничем не отличается между японской и, например, русской культурой. Но его можно установить руками! Радостно потирая руками, пробуем изобразить нужный код:
var cultureInfo = new CultureInfo("ja-JP")
{
NumberFormat = new NumberFormatInfo
{
NativeDigits = new[]
{
"〇",
"一",
"二",
"三",
"四",
"五",
"六",
"七",
"八",
"九"
}
}
};
var str = string.Format(cultureInfo, "{0}", 100);
Console.WriteLine(str);
Хм… Runtime exception с какой-то нелепой жалобой, что мы ему вовсе не циферки подсовываем… Смотрим в стандарт Unicode 5.1.0, Mathematical Symbols→Numbers and Digits… Забавно, и правда что, юникод не считает вышеозначенные идеограммы числами.
Что ж, снова придётся всё писать самому. К тому же, заодно можно будет сделать полноценный форматер, который будет генерировать минимальную запись без всяких иностранных нулей
Исходник лежит здесь: NumberConverter.cs. Не самое элегантное решение, зато достаточно компактное. Да, игнорирует отрицательные числа (ну не надо мне это), да работает для чисел до 999999999999999 включительно, хотя мне надо только от 1 до 9 (^_^)"… главное, что работает
