Могу ли я иметь переменное количество общих параметров?
В моем проекте у меня есть следующие три интерфейса, которые реализуются классами, которые управляют слиянием различных бизнес-объектов, имеющих разную структуру.
public interface IMerger<TSource, TDestination>
{
TDestination Merge(TSource source, TDestination destination);
}
public interface ITwoWayMerger<TSource1, TSource2, TDestination>
{
TDestination Merge(TSource1 source1, TSource2 source2, TDestination destination);
}
public interface IThreeWayMerger<TSource1, TSource2, TSource3, TDestination>
{
TDestination Merge(TSource1 source1, TSource2 source2, TSource3 source3, TDestination destination);
}
Это хорошо работает, но я бы предпочел один IMerger
интерфейс, который определяет переменное число TSource
параметры, что-то вроде этого (пример ниже использует params
; Я знаю, что это не действительный C#):
public interface IMerger<params TSources, TDestination>
{
TDestination Merge(params TSource sources, TDestination destination);
}
Есть ли способ добиться этого или что-то функционально эквивалентное?
5 ответов
Ты не можешь Это ключевая часть API. Вы можете, однако, сделать что-то вокруг, например, принять Type[]
аргумент. Вы могли бы также придумать какой-нибудь экзотический способ "плавного API / расширения", но, честно говоря, это, вероятно, не будет стоить того; но что-то вроде:
obj.Merge<FirstType>(firstData).Merge<SecondType>(secondData)
.Merge<ThirdType>(thirdData).Execute<TDestination>(dest);
или с выводом универсального типа:
obj.Merge(firstData).Merge(secondData).Merge(thirdData).Execute(dest);
Каждый шаг слияния будет просто хранить работу, доступную только для Execute
,
Это зависит от того, хотите ли вы, чтобы ваши объекты могли объединять объекты разных типов или нет.
Для однородного слияния все, что вам нужно, это:
public interface IMerger<TSource, TDestination> {
TDestination Merge(IEnumerable<TSource> sources, TDestination destination);
}
Для гетерогенного объединения рассмотрите требование, чтобы все типы источников были производными от общего базового типа:
public interface IMerger<TSourceBase, TDestination> {
TDestination Merge(IEnumerable<TSourceBase> sources, TDestination destination);
}
Я не вижу необходимости в массиве параметров, просто передайте коллекцию объектов.
Параметры могут быть только в конце или в списке аргументов и являются синтаксическим сахаром для массива:
public interface IMerger<TSources, TDestination>
{
TDestination Merge(TDestination destination, params TSource[] sources);
}
Если вы хотите разрешить использование любого типа, просто используйте object[]
вместо TSource.
Примечание: у MS была эта "проблема" также, когда они делали Expression. Они придумали кучу делегатов Action<>
а также Func<>
с различным числом общих аргументов, но каждый делегат на самом деле является другим типом.
Сегодня я работал над соглашением по автоматизации MEF, он использует способ создания переменных общих входных параметров, инкапсулированных в делегаты:S
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
namespace MEFHelper
{
public static class MEFImporter
{
#region Catalog Field
private readonly static AggregateCatalog _catalog;
public static AggregateCatalog Catalog { get { return _catalog; } }
#endregion
static MEFImporter()
{
//An aggregate catalog that combines multiple catalogs
_catalog = new AggregateCatalog();
//Adds all the parts found in all assemblies in
//the same directory as the executing program
_catalog.Catalogs.Add(
new DirectoryCatalog(
System.IO.Path.GetDirectoryName(new Uri(
System.Reflection.Assembly.GetExecutingAssembly()
.CodeBase).AbsolutePath)
));
}
/// <summary>
/// Fill the imports of this object
/// </summary>
/// <param name="obj">Object to fill the Imports</param>
/// <param name="contructorParameters">MEF contructor parameters</param>
/// <remarks>Use for MEF importing</remarks>
public static void DoImport(this object obj, params MEFParam[] contructorParameters)
{
//Create the CompositionContainer with the parts in the catalog
CompositionContainer container = new CompositionContainer(Catalog, true);
//Add the contructor parameters
if (contructorParameters != null && contructorParameters.Length > 0)
{
foreach (MEFParam mefParam in contructorParameters)
if (mefParam != null && mefParam.Parameter != null) mefParam.Parameter(container);
}
//Fill the imports of this object
container.ComposeParts(obj);
}
#region MEFParam
/// <summary>
/// Creates a Mef Param to do the Import
/// </summary>
/// <typeparam name="T">Type of the value to store</typeparam>
/// <param name="value">Value to store</param>
/// <param name="key">Optional MEF label</param>
/// <returns>A MEF paramameter</returns>
/// <remarks>This retuns a MEF encapsulated parameter in a delegate</remarks>
public static MEFParam Parameter<T>(T value, string key = null)
{
Action<CompositionContainer> param;
if (string.IsNullOrWhiteSpace(key))
param = p => p.ComposeExportedValue(value);
else param = p => p.ComposeExportedValue(key, value);
return new MEFParam(param);
}
/// <summary>
/// Mef Param to do the Import
/// </summary>
public class MEFParam
{
protected internal MEFParam(Action<CompositionContainer> param)
{
this.Parameter = param;
}
public Action<CompositionContainer> Parameter { get; private set; }
}
#endregion
}
}
я использую этот инструмент для общего импорта и разрешения объектов MEF с помощью экстензора (интересно), насмешки: вы можете добавить параметры конструктора импорта, проблема заключается в функции ComposeExportedValue, которая использует универсальный параметр, вы не можете добавить это в переменной params в функции, с этой техникой, да! если вы попытаетесь проверить: например...
public class Factory : IDisposable
{
[Import(typeof(IRepository))]
private Repository _repository = null;
public Factory()
{
MEFImporter.DoImport(this, MEFImporter.Parameter("hello"));
}
public IRepository Repository
{
get
{
return _repository;
}
}
public void Dispose()
{
_repository = null;
}
}
--- в другой сборке
[Export(typeof(IRepository))]
public class Repository : IRepository
{
string Param;
[ImportingConstructor]
public Repository(string param)
{
//add breakpoint
this.Param = param;
}
}
params
Ключевое слово используется только в сигнатуре метода, это не то, чем вы можете украсить тип. Итак, тип по-прежнему просто TSources
и вы должны поместить параметр, украшенный params
последняя в сигнатуре метода:
public interface IMerger<TSources, TDestination> {
TDestination Merge(TDestination destination, params TSources[] sources);
}