【问题标题】:Can I have a variable number of generic parameters?我可以有可变数量的泛型参数吗?
【发布时间】:2010-12-09 10:22:12
【问题描述】:

在我的项目中,我有以下三个接口,它们由管理合并具有不同结构的各种业务对象的类实现。

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

有什么方法可以实现这一点,或者功能上等效的东西吗?

【问题讨论】:

  • 你不能使用可变数量的参数创建一个接口,但你可以为所有变体赋予相同的名称(就像 Func 委托一样)
  • 虽然这只是一个无效的伪代码,但像public interface IMerger&lt;TDestination, params TSources&gt; { TDestination Merge(TDestination destination, params TSource sources); } 这样写更有意义。 params 应该排在最后。但正如 Christian Hayter 所说,拥有基类是唯一的解决方法

标签: c# .net generics parameters


【解决方案1】:

你不能。这是 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 可以访问。

【讨论】:

  • 此外,这种方法可能表明合并的合理“排序”,这在我们的场景中是不正确的。
  • 但是,问题中的“TSource1 source1, TSource2 source2, TSource3 source3”也是如此......
  • 我的想法是 source1 .. sourcen 表示“这些是源”,而 .Merge 调用链表示合并是按特定顺序进行的。
【解决方案2】:

这取决于您是否希望您的对象能够合并不同类型的对象。

对于同构合并,您只需要以下内容:

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

对于异构合并,考虑要求所有源类型都派生自一个公共基类型:

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

我认为不需要参数数组,只需传入对象集合即可。

【讨论】:

    【解决方案3】:

    参数只能在末尾或参数列表中,是数组的语法糖:

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

    如果您想允许使用任何类型,只需使用 object[] 而不是 TSource。

    注意: MS 在做 Expression 的时候也有这个“问题”。他们提出了一堆代表Action&lt;&gt;Func&lt;&gt; 的泛型参数数量不同,但实际上每个代表都是另一种类型。

    【讨论】:

    • 我使用参数的示例实际上不是有效的 C#,因为您不能在这种情况下使用参数,已更新我的帖子以使其更清晰
    【解决方案4】:

    params 关键字只用在方法签名中,它不是你可以用来装饰类型的东西。所以,类型仍然只是TSources,你必须把用params修饰的参数放在方法签名的最后:

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

    【讨论】:

    • 这是如何回答问题的? TSource1TSource2 等如何等同于 TSources?我认为你已经从他发布的 OP 的最后一个示例(非工作伪代码)中得到了启发。
    【解决方案5】:

    今天,我参与了一项让 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,您可以'不要将它添加到函数的变量参数中,使用这种技术,是的! 如果您尝试测试:例如...

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-11-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多