Появилась у меня необходимость при сборке приложения передать внутрь набор констант для использования их на рантайме. Например, мы хотим «зашить» в приложение некое строковое значение которое будет известно в момент сборки.
В мире C++ я решал подобные штуки очень просто с использованием define и опций компилятора. Но в .Net define не имеют значений кроме true/false, т.е. они либо определены либо нет. Насколько я понимаю, их цель это простейшая условная компиляция.
Кому интересно решение добро пожаловать под кат.
Первая мысль, которая меня посетила: перед билдом сгенерить файлик с константами по шаблону, но хотелось бы обойтись без привлечения тяжелой артиллерии шаблонизаторов.
После некоторых поисков я обнаружил интересный факт. В .Net есть механизм атрибутов. Эти атрибуты можно цеплять к классам, методам, полям и всяким разным сущностям. Оказалось что его можно цепануть ко всей сборке целиком.
В файлах проектов (.csproj) есть возможность выставлять значения этим атрибутам при сборке. А в MSBuild можно передать параметры извне через механизм пропертей. Вроде все сходится, надо пробовать.
Создаем новое консольное приложение:
% mkdir Example && cd Example
% dotnet new console
Создаем файл ExampleAttribute.cs с определением нашего атрибута.
using System;
namespace Example
{
[AttributeUsage(AttributeTargets.Assembly)] //Указываем что атрибут цепляется к сборке
public class ExampleAttribute : Attribute
{
public string Value { get; set; }
public ExampleAttribute(string value)
{
Value = value;
}
}
}
Далее файл Example.csproj приводим к следующему виду. Я добавил комментарии, дабы суть изменений была ясна.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>Example</RootNamespace>
<ExampleValue>default</ExampleValue> <!-- Добавляем нашу кастомную пропертю с дефолтным значением -->
</PropertyGroup>
<!-- Добавляем этот блок для опледеления значения атрибута при сборке -->
<ItemGroup>
<AssemblyAttribute Include="Example.ExampleAttribute"> <!-- Указываем класс атрибута -->
<_Parameter1>$(ExampleValue)</_Parameter1> <!-- Выставляем значения для вызова конструктора -->
</AssemblyAttribute>
</ItemGroup>
</Project>
Ну и собственно получение значения на рантайме в Project.cs
using System;
using System.Reflection;
namespace Example
{
class Program
{
static void Main(string[] args)
{
var assembly = Assembly.GetExecutingAssembly();
var attr = (ExampleAttribute) assembly.GetCustomAttribute(typeof(ExampleAttribute));
Console.WriteLine($"Assembly attribute value = '{attr.Value}'");
}
}
}
Итак соберем и запустим, дабы посмотреть что у нас получилось.
% dotnet build .
% dotnet run --no-build .
Assembly attribute value = 'default'
А теперь с параметром:
% dotnet build . /p:ExampleValue="NOT DEFAULT"
% dotnet run --no-build .
Assembly attribute value = 'NOT DEFAULT'
Вуаля, цель достигнута. Пользуйтесь на здоровье.
Автор: mpak65536