Могу ли я иметь переменное количество общих параметров?

В моем проекте у меня есть следующие три интерфейса, которые реализуются классами, которые управляют слиянием различных бизнес-объектов, имеющих разную структуру.

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);
}
Другие вопросы по тегам