web-dev-qa-db-fra.com

Puis-je avoir un nombre variable de paramètres génériques?

Dans mon projet, les trois interfaces suivantes sont implémentées par des classes qui gèrent la fusion de divers objets métier ayant différentes structures.

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);
}

Cela fonctionne bien, mais je préférerais une interface IMerger qui spécifie un nombre variable de paramètres TSource, un peu comme ceci (l'exemple ci-dessous utilise params; je sais que cela n'est pas valide C #):

public interface IMerger<params TSources, TDestination>
{
    TDestination Merge(params TSource sources, TDestination destination);
}

Y at-il un moyen d’y parvenir, ou quelque chose de fonctionnellement équivalent?

35
Richard Everett

Tu ne peux pas. C'est un élément clé de l'API. Vous pouvez toutefois faire quelque chose de côté, par exemple accepter un argument Type[]. Vous pouvez également imaginer une méthode exotique de "méthode API/extension fluide" exotique, mais pour être honnête, cela ne vaudra probablement pas la peine; mais quelque chose comme:

obj.Merge<FirstType>(firstData).Merge<SecondType>(secondData)
     .Merge<ThirdType>(thirdData).Execute<TDestination>(dest);

ou avec inférence de type générique:

obj.Merge(firstData).Merge(secondData).Merge(thirdData).Execute(dest);

Chaque étape de la fusion stockerait simplement le travail à effectuer, accessible uniquement par Execute.

28
Marc Gravell

Cela dépend si vous voulez que vos objets puissent fusionner des objets de types différents ou non.

Pour une fusion homogène, tout ce dont vous avez besoin est la suivante:

public interface IMerger<TSource, TDestination> {
    TDestination Merge(IEnumerable<TSource> sources, TDestination destination);
}

Pour une fusion hétérogène, envisagez d'obliger tous les types de source à dériver d'un type de base commun:

public interface IMerger<TSourceBase, TDestination> {
    TDestination Merge(IEnumerable<TSourceBase> sources, TDestination destination);
}

Je ne vois pas la nécessité d'un tableau de paramètres, mais simplement de la collection d'objets.

5
Christian Hayter

Les paramètres ne peuvent figurer qu’à la fin de la liste ou des listes d’arguments et constituent un sucre syntaxique pour un tableau:

public interface IMerger<TSources, TDestination>
{
  TDestination Merge(TDestination destination, params TSource[] sources);
}

Si vous souhaitez autoriser l'utilisation de tout type, utilisez simplement object[] au lieu de TSource.

Note: MS avait ce "problème" aussi quand ils ont fait le truc d'Expression. Ils ont mis au point un groupe de délégués Action<> et Func<> avec un nombre différent d'arguments génériques, mais chaque délégué est un autre type.

4
Lucero

Le mot clé params n'est utilisé que dans une signature de méthode, ce n'est pas quelque chose avec lequel vous pouvez décorer un type. Donc, le type est toujours juste TSources, et vous devez placer le paramètre décoré avec params last dans la signature de la méthode:

public interface IMerger<TSources, TDestination> {
    TDestination Merge(TDestination destination, params TSources[] sources);
}
0
Guffa

Aujourd'hui, j'ai travaillé sur un accord pour automatiser MEF. Cette méthode utilise un moyen de créer des paramètres d'entrée génériques variables, encapsulés dans des délégués: 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

    }
}

j'utilise cet outil pour importer et résoudre les objets MEF de manière générique avec un extenseur (intéressant), la raillerie: vous pouvez éventuellement ajouter les paramètres du constructeur d'importation, le problème est dans la fonction ComposeExportedValue qui utilise un paramètre générique, vous ne pouvez pas ajouter. ceci dans un param variable dans une fonction, avec cette technique, oui! si vous essayez de tester: par exemple ...

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;
    }
}

--- Dans une autre assemblée

[Export(typeof(IRepository))]
public class Repository : IRepository
{
     string Param;

     [ImportingConstructor]
     public Repository(string param)
     {
         //add breakpoint
         this.Param = param;
     }
}
0
ModMa