Я думаю, что вы ищете возможность сортировать по зависимостям.Я использовал нечто подобное, в результате чего я создал объект Bootstrapper для управления запуском приложения.Этот Bootstrapper поддерживает 0 или более задач начальной загрузки, которые могут иметь или не иметь зависимости.Я решил это, создав новый тип коллекции, DependencyList<TModel, TKey>
, который позволяет добавлять произвольное количество элементов и автоматически сортировать при первом перечислении или после любых последующих изменений коллекции.
С точки зрения того, что вы хотите сделать, мы можем воспользоваться как этим новым типом списка, так и пользовательской информацией экспорта MEF.Первое, с чего мы начнем, это наш плагин базового конвейерного контракта:
public interface IPipelinePlugin
{
void Process(PipelineContext context);
}
public abstract class PipelinePluginBase : IPipelinePlugin
{
public virtual void Process(PipelineContext context)
{
}
}
Я предпочитаю добавлять абстрактный класс, чтобы сопровождать его, поэтому, если мне нужно, я могу ввести некоторую базовую разделяемую логику, не нарушаясуществующие плагины.
Следующее, что мы сделаем, определим контракт метаданных, а затем пользовательский атрибут экспорта:
public interface IPipelinePluginMetadata
{
string Name { get; }
string[] Dependencies { get; }
string[] Pipelines { get; }
}
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Method, AllowMultiple = false)]
public class PipelineAttribute : ExportAttribute, IPipelinePluginMetadata
{
public PipelineAttribute(string name)
: base(typeof(IPipelinePlugin))
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("A pipeline plugin requires a name.", "name");
Name = name;
}
public string Name { get; private set; }
public string[] Dependencies { get; set; }
public string[] Pipelines { get; set; }
}
С помощью пользовательского атрибута экспорта я могу определить сформируйте моего экспорта, чтобы убедиться, что все они экспортируют правильную информацию.
Далее, давайте посмотрим на пользовательский плагин.Давайте предположим, что мы хотим создать конвейер для применения украшений BBCode к нашему входному тексту, поэтому, во-первых, простой плагин:
[Pipeline("ApplyColour", Pipelines = new[] { "bbcode" })]
public class ApplyColourPipelinePlugin : PipelinePluginBase
{
public override void Process(PipelineContext context)
{
context.Content = "[color=f00]" + context.Content + "[/color]";
}
}
В приведенном выше примере просто упаковывается входной текст в теги [color]
.Атрибут Pipeline
указывает имя плагина (ApplyColour
) и то, для каких конвейеров сделать плагин доступным, в данном случае bbcode
.Вот более сложный пример:
[Pipeline("MakeBold", Pipelines = new[] { "bbcode" })]
public class MakeBoldPipelinePlugin : PipelinePluginBase
{
public override void Process(PipelineContext context)
{
context.Content = "[b]" + context.Content + "[/b]";
}
}
[Pipeline("MakeItalic", Dependencies = new[] { "MakeBold" }, Pipelines = new[] { "bbcode" })]
public class MakeItalicAfterBoldPipelinePlugin : PipelinePluginBase
{
public override void Process(PipelineContext context)
{
context.Content = "[i]" + context.Content + "[/i]";
}
}
В приведенном выше примере я детализирую два дополнительных плагина, один из которых выделяет текст жирным шрифтом, а другой курсивом. Но , я ввел требование зависимости и сказал нашей системе плагинов, что MakeItalic
зависит от MakeBold
.Вот как мы это собрали:
[Export]
public class PipelineManager
{
[ImportMany]
public IEnumerable<Lazy<IPipelinePlugin, IPipelinePluginMetadata>> Plugins { get; set; }
public Queue<IPipelinePlugin> BuildPipeline(string name)
{
// Get the plugins.
var plugins = Plugins
.Where(p => p.Metadata.Pipelines == null || p.Metadata.Pipelines.Contains(name)).ToList();
// Create our dependency list.
var dependencyList = new DependencyList<Lazy<IPipelinePlugin, IPipelinePluginMetadata>, string>(
l => l.Metadata.Name,
l => l.Metadata.Dependencies);
// Add each available plugin to the list.
plugins.ForEach(dependencyList.Add);
// Create our pipeline.
var pipeline = new Queue<IPipelinePlugin>();
// Now, when we enumerate over it, it will be sorted.
dependencyList.ForEach(p => pipeline.Enqueue(p.Value));
return pipeline;
}
}
Наш тип PipelineManager
работает от MEF, поэтому он будет [Import]
серией IPipelinePlugin
экземпляров вместе со связанными с ними метаданными (которые должны иметь формубыть проектируемым как IPipelinePluginMetadata
).Имея это в виду, здесь он используется:
class Program
{
static void Main(string[] args)
{
var container = new CompositionContainer(new AssemblyCatalog(typeof(Program).Assembly));
var manager = container.GetExportedValue<PipelineManager>();
var pipeline = manager.BuildPipeline("bbcode");
var context = new PipelineContext("Hello World");
foreach (var plugin in pipeline)
plugin.Process(context);
Console.Write(context.Content);
Console.ReadKey();
}
}
Действительно, список зависимостей и ваш проект конвейера - это две отдельные области, на которые нужно смотреть, но я надеюсь, что это даст вам представление о том, как вы мог бы использовать это.
Я выдал это также как Гист (https://gist.github.com/1752325).