通用方法的多重约束?

4

我有以下四种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));
}

首先,我不能使用字符串作为限制条件,因为它是密封的。其次,我不认为我可以以这种方式指定多个约束条件。是否有任何解决方案?

我“认为”这就是你想要的:https://dev59.com/0HA75IYBdhLWcg3wm6ex - Derek Strickland
所以第一和第二个重载调用第三个重载。第四个重载调用第三个,而第三个调用某个未知的第五个重载? - Dudemanword
Add(TrackInfo)是Collection <TrackInfo>成员。 - markovcd
@DerekStrickland 不太可能。我认为 OP 想要通过泛型类型来神奇地调用一个非泛型重载 Add(...TrackInfo track)Add(...string path),但这需要一些运行时解析 - 使用巧妙放置的 dynamic 是常见的解决方案…… - Alexei Levenkov
正如我所想的那样。解决“问题”的方法会大大降低可读性。我将坚持使用多个重载。 - markovcd
5个回答

2

虽然不是最好的答案,但我认为这可能是你想要的:

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

1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - KeithS

1
你可以通过向音轨信息类添加一个隐式转换来避免不断的构造。 public static implicit operator TrackInfo(string s) { return new TrackInfo(s); }
从IEnumerable 到IEnumerable 需要一个显式的扩展方法进行转换。我建议您保留原样。

1
您可以指定多个通用类型约束;但是,这样做时,用于关闭通用类型的类型必须满足所有约束。这在使用TrackInfo和字符串时是不可能的,因为它们都是“具体”类型(类),它们之间没有继承层次结构(System.String类是密封的),因此没有类型可以从两个类继承。
按照现有代码编写看起来很好。您可以通过在方法调用中简单地使用一个字符串构造TrackInfo(或使用第二个Select从每个字符串构造TrackInfo)来从IEnumerable<string>重载中调用“主要重载”(接受单个TrackInfo的重载),以使调用栈更短一些。
一个问题:在Select中使用的lambda函数通常不应该产生副作用;显然,在委托中使用的Add()方法除了返回输入元素的“投影”之外还执行其他操作,但出于代码风格的考虑,我仍然更喜欢将投影和添加元素分开的代码,即使最终代码会更冗长。

0

由于这部分代码 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;
}

0

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

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

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

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

每个单独的操作由Action定义,通过调用链传播。

对于删除:

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

添加:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接