Posts Tagged ‘resharper’
Лайфхак с использованием строковых литералов
Хочу рассказать про лайфхак с использованием стороковых литералов. Строковый литерал сам по себе – достаточно унылая вещь, но есть один нюанс – лексер языкового сервиса (если у вас такой имеется) хорошо понимает начало и конец литерала, и может разом выдать содержимое как System.String. А получив полноценную CLR-строку, с ней можно очень много всего сделать.
Да, сразу подчеркну – лайфхак этот не очень-то зависит от языка, хотя конечно я его использую в C#.
Строка как магический псевдоконструктор
Первое наблюдение – это то что многие объекты поддерживают метод Parse() – не только int или decimal, но такие объекты как DateTime, TimeSpan, XElement и так далее. Это значит что написав строку, отдаленно похожую на дату, я могу сконвертировать ее в правильный вызов конструктора DateTime:

Особенно эффектно это выглядит для XML, где сложный кусок этого языка может превратиться в красивую цепочку из конструкторов XElement, XAttribute и так далее.
Строка как неживая часть кода
Языки можно мешать – например HTML и C# в MVCшных вьюшках. И если вы не боитесь временно – примерно на долю секунду которая нужна чтобы выполнить контекстное действие – потерять контроль над кодом, то кто мешает вам, например, делать string splicing в PHP-стиле, но в коде C#?
Вот что я имею ввиду:

В примере выше, мы вставили идентификаторы C# прямо в строку, а потом вызвали контекстное действие, которое заменило всю строку на правильный вызов String.Format().
Строка как мимолетно существующий DSL
В погоне за оператором ?. (цепочная процерка на null), многие сели на Roslyn и аналогичные вещи. Но ведь проблема цепочной проверки может очень просто решиться если вместо введения оператора мы дороворимся, что любое выражение в форме a.b.c может быть написано как строковый литерал и, мановением волшебной палочки превратиться в
a == null ? null : (a.b == null ? null : a.b.c)
Ну или что-то в этом духе. Опять же, строка которая толком ничего не означает может быть развернута вот в такое. Код, конечно, не очень читабельный. Но всяко лучше чем пытаться делать то же самое руками.
Внимательные читатели заметят, что то же самое можно извлечь и из вполне валидного идентификатора. Что ж, не спорю. Мне просто лень по нему гулять – для меня сделать string.Split() намного проще :)
Подобных встроенных DSLов можно сделать море. Например, можно взять нотацию HTML Zen и из этого строкового литерала создать декларацию XLinq которая соответсвует исходному коду. Возможности безграничны.
Как это работает
Сразу скажу – реализовать подобное тривиально. Для Решарпера, нужно просто контекстное действие, которое говорит вам что вы «на строке», создает новый кусок кода, и делает обмен:
[ContextAction(Group = "C#", Name = "Hide string value",
Description = "Ensures the string is not human-readable in code.",
Priority = 15)]
public class StringHideRealValueCA : IContextAction
{
private readonly IList<IBulbItem> items = new List<IBulbItem>();
private readonly ICSharpContextActionDataProvider provider;
public StringHideRealValueCA(ICSharpContextActionDataProvider provider)
{
this.provider = provider;
}
public bool IsAvailable(IUserDataHolder cache)
{
var e = provider.GetSelectedElement<ICSharpLiteralExpression>(true, true);
if (e != null && e.IsConstantValue())
{
if (e.ConstantValue.IsString())
{
var s = (string)e.ConstantValue.Value;
if (!string.IsNullOrEmpty(s))
items.Add(new StringHideRealValueImpl(provider, e));
}
}
return items.Count > 0;
}
public IBulbItem[] Items
{
get { return items.ToArray(); }
}
}
В коде выше, StringHideRealValueProvider прячет естественную строку, подменяя ее Base64-закодированной строкой. Вот, собственно, реализация:
private class StringHideRealValueImpl
{
// очевидные методы опущены
protected override Action<ITextControl> ExecutePsiTransaction(ISolution solution, IProgressIndicator progress)
{
CSharpElementFactory factory = CSharpElementFactory.GetInstance(provider.PsiModule, true);
var value = literal.ConstantValue.Value as string;
if (value != null)
{
string encoded = Convert.ToBase64String(Encoding.Unicode.GetBytes(value));
ICSharpExpression ex = factory.CreateExpressionAsIs(
string.Format("System.Text.Encoding.Unicode.GetString(System.Convert.FromBase64String(\"{0}\"))", encoded));
literal.ReplaceBy(ex);
}
return
tc => provider.PsiFile.OptimizeImportsAndRefs(
provider.Document.DocumentRange.CreateRangeMarker(provider.Document), false, true, progress);
}
}
Если коротко, в замену существующему литералу создается выражение (через вызов CreateExpressionAsIs), а потом банально втыкается в замен существующей строки. На базе этого вы можете писать свои механизмы подмены – это тривиально!
Заключение
Пример выше – это маленький лайфхак который иногда помогает писать код быстрее. String splicing особенно полезен, хотя нужно приучить себя им пользоваться. Помните – эти строки только несколько секунд «магические», а потом перестают существовать совсем. Так что ничего страшного.
