【问题标题】:Multiple constraints on generic methods?泛型方法的多重约束?
【发布时间】:2015-05-27 23:57:30
【问题描述】:

我有以下四个重载的方法Add:

    public IEnumerable<TrackInfo> Add(DataContext dataContext, IEnumerable<TrackInfo> tracks)
    {
        return tracks.Select(t => Add(dataContext, t));
    }

    public IEnumerable<TrackInfo> Add(DataContext dataContext, IEnumerable<string> files)
    {
        return files.Select(f => Add(dataContext, f));
    }

    public TrackInfo Add(DataContext dataContext, TrackInfo track)
    {
        dataContext.TrackInfos.InsertOnSubmit(track);
        Add(track);
        return track;
    }

    public TrackInfo Add(DataContext dataContext, string path)
    {
        return Add(dataContext, new TrackInfo(path));
    }

有没有办法让第一次和第二次重载成为泛型函数?其他一些抽象机制也会有所帮助。

澄清我的意思(这不会编译!):

public IEnumerable<TrackInfo> Add<T>(DataContext dataContext, IEnumerable<T> items) where T : TrackInfo, string
{
    return items.Select(i => Add(dataContext, i));
}

首先,我不能使用字符串作为约束,因为它是密封的。其次,我认为我不能以这种方式指定多个约束。有什么解决办法吗?

【问题讨论】:

  • 我“认为”这就是你想要的stackoverflow.com/questions/3197849/…
  • 所以第一个和第二个重载调用第三个重载。第四个重载调用第三个,第三个调用未知的第五个重载?
  • Add(TrackInfo) 是 Collection 成员。
  • @DerekSrickland 不太可能。我认为 OP 想要基于泛型类型神奇地调用非泛型覆盖 Add(...TrackInfo track)Add(...string path) 之一,但如果没有一些运行时分辨率就无法完成 - 使用巧妙放置的 dynamic 是常见的解决方案。 ..
  • 和我想的一样。解决“问题”会大大降低可读性。我会坚持使用多个重载。

标签: c# generics ienumerable


【解决方案1】:

不是最佳答案,但我认为这可能是您想要的:

public IEnumerable<TrackInfo> Add<T>(DataContext dataContext, IEnumerable<T> tracks) where T : class
{
    if(typeof(T) == typeof(string)) 
    {
        return tracks.Select(t => Add(dataContext, new TrackInfo(t)));
    }
    else if(typeof(T) == typeof(TrackInfo)) 
    {
        return tracks.Select(t => Add(dataContext, t as TrackInfo));
    }
    else 
    {
        throw new ArgumentException("The type must be string or TrackInfo");
    }
}

public TrackInfo Add(DataContext dataContext, TrackInfo track)
{
    dataContext.TrackInfos.InsertOnSubmit(track);
    Add(track);
    return track;
}

// you may not need this
public TrackInfo Add(DataContext dataContext, string path)
{
    return Add(dataContext, new TrackInfo(path));
}

【讨论】:

  • 目前有效,但是当派生 TrackInfo 以创建 MP3TrackInfo 和 CDTrackInfo 时会发生什么?考虑使用Type.IsAssignableFrom() 以确保您的第二个条件正确处理 TrackInfo 的子项。
【解决方案2】:

您可以通过向轨道信息类添加隐式转换来避免常量构造。

public 静态隐式运算符 TrackInfo(string s) { 返回新的 TrackInfo(s); }

从 IEnumerable 到 IEnumerable 需要显式的扩展方法进行转换。我会把它留给你。

【讨论】:

    【解决方案3】:

    您可以指定多个泛型类型约束;但是,当您这样做时,用于关闭泛型的类型必须满足所有约束。这对于 TrackInfo 和字符串是不可能的,因为它们都是“具体”类型(类),它们之间没有继承层次结构(System.String 类是密封的),因此没有类型可以继承自两个类。

    编写的这段代码对我来说看起来不错。您可以通过简单地从方法调用中的字符串构造一个 TrackInfo(或使用第二个 Select从每个字符串构造 TrackInfo)。

    一种气味; Select 中使用的 lambda 通常不会产生副作用;很明显,委托中使用的 Add() 方法除了返回输入元素的“投影”之外还做了其他事情,但是对于样式我仍然更喜欢分离投影和添加元素的代码,即使它结束了更详细。

    【讨论】:

      【解决方案4】:

      由于这部​​分new TrackInfo(path),您不能完全通用。可以通过重写第二个方法来删除最后一个方法:

      public IEnumerable<TrackInfo> Add(DataContext dataContext, IEnumerable<TrackInfo> tracks)
      {
          return tracks.Select(t => Add(dataContext, t));
      }
      
      public IEnumerable<TrackInfo> Add(DataContext dataContext, IEnumerable<string> files)
      {
          return Add(dataContext, files.Select(f => new TrackInfo(f));
      }
      
      public TrackInfo Add(DataContext dataContext, TrackInfo track)
      {
          dataContext.TrackInfos.InsertOnSubmit(track);
          Add(track);
          return track;
      }
      

      【讨论】:

        【解决方案5】:

        受到 Wery Nguyen 的启发,并在我的课堂上进行了全面的泛型重构。我最初发布的片段并不是问题的全部,但我认为它可以解释我的意思。我应该把整件事都贴出来。

        我想出了这个:

            #region Private methods
        
            static IEnumerable<TrackInfo> ProcessItems<T>(IEnumerable<T> items, Func<DataContext, IEnumerable<T>, IEnumerable<TrackInfo>> func)
            {
                using (var dataContext = new DataContext())
                {
                    foreach (var item in func(dataContext, items))
                    {
                        yield return item;
                    }
        
                    dataContext.SubmitChanges();
                }
            }
        
            static IEnumerable<TrackInfo> ProcessItems<T>(DataContext dataContext, IEnumerable<T> items, Func<DataContext, T, TrackInfo> func)
            {
                return items.Select(t => func(dataContext, t));
            }
        
            TrackInfo ProcessItem<T>(DataContext dataContext, T item, Action<TrackInfo> action)
            {
                if (typeof(T) == typeof(string))
                {
                    return ProcessItem(dataContext, this[item as string], action);
                }
        
                if (typeof(T) == typeof(TrackInfo))
                {
                    var track = item as TrackInfo;
                    action(track);
                    return track;
                }
        
                throw new ArgumentException("The type must be string or TrackInfo");
            }
        
            #endregion
        
        
            #region Public methods
        
            public IEnumerable<TrackInfo> Add<T>(IEnumerable<T> items)
            {
                return ProcessItems(items, Add);
            }
        
            public IEnumerable<TrackInfo> Add<T>(DataContext dataContext, IEnumerable<T> items)
            {
                return ProcessItems(dataContext, items, Add);
            }
        
            public TrackInfo Add<T>(DataContext dataContext, T item)
            {
                return ProcessItem(dataContext, item, 
                    i =>
                    {
                        dataContext.TrackInfos.InsertOnSubmit(i);
                        Add(i);
                    });
            }
        
            public IEnumerable<TrackInfo> Delete<T>(IEnumerable<T> items)
            {
                return ProcessItems(items, Delete);
            }
        
            public IEnumerable<TrackInfo> Delete<T>(DataContext dataContext, IEnumerable<T> items)
            {
                return ProcessItems(dataContext, items, Delete);
            }
        
            public TrackInfo Delete<T>(DataContext dataContext, T item)
            {
                return ProcessItem(dataContext, item,
                    i =>
                    {
                        dataContext.TrackInfos.Attach(i);
                        dataContext.TrackInfos.DeleteOnSubmit(i);
                        Remove(i);
                    });
            }
        

        代码中没有冗余,但我不确定可读性是否那么好。尽管如此,这是一次有趣的学习经历。

        只是为了解释。有两个通用 ProcessItem 和一个 ProcessItem。每一个都是 Add 和 Delete 连续重载的基本方法。它们是:

        • 添加/删除单个项目,
        • 添加/删除多个项目,
        • 通过创建 DataContext 添加/删除多个项目。

        每个奇异操作都由通过调用链传播的 Action 定义。 删除:

        dataContext.TrackInfos.Attach(i);
        dataContext.TrackInfos.DeleteOnSubmit(i);
        Remove(i);
        

        添加:

        dataContext.TrackInfos.InsertOnSubmit(i);
        Add(i);
        

        【讨论】:

          猜你喜欢
          • 2012-06-05
          • 1970-01-01
          • 2012-02-19
          • 2016-11-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多