泛型方法的多重约束?
Multiple constraints on generic methods?
我有以下四个方法重载:
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));
}
首先,我不能使用字符串作为约束,因为它是密封的。其次,我不认为我可以通过这种方式指定多个约束。有什么解决办法吗?
由于这部分 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;
}
您可以通过在曲目信息中添加隐式转换来避免常量构造 class。
public static implicit operator TrackInfo(string s)
{
return 新曲目信息;
}
从 IEnumerable 到 IEnumerable 将需要显式扩展方法进行转换。我会把它留给你。
不是最佳答案,但我认为这可能是您想要的:
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 和字符串是不可能的,因为它们都是 "concrete" 类型(classes),它们之间没有可能的继承层次结构(System.String class 是密封的)所以没有类型可以从两个 classes.
继承
这段代码对我来说看起来不错。您可以通过简单地从方法调用中的字符串构造 TrackInfo 来从 IEnumerable<string>
重载调用 "primary overload"(采用单个 TrackInfo
的方法)来缩短调用堆栈(或者用第二个 Select 从每个字符串构造 TrackInfo)。
一种气味; Select 中使用的 lambda 表达式通常不应产生副作用;很明显,委托中使用的 Add() 方法除了对输入元素执行 return a "projection" 之外的操作,但对于样式,我仍然更喜欢将投影和添加分开的代码元素,即使它最终变得更加冗长。
受到 Wery Nguyen 的启发,并在我的 class 中进行了全面的通用重构。我最初发布的片段并不是全部问题,但我认为它可以解释我的意思。我应该把整个事情都贴出来。
我想到了这个:
#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 的基本方法。它们是:
- adding/removing 单品,
- adding/removing 多项,
- adding/removing 创建 DataContext 的多个项目。
每个单独的操作都由通过调用链传播的 Action 定义。
删除:
dataContext.TrackInfos.Attach(i);
dataContext.TrackInfos.DeleteOnSubmit(i);
Remove(i);
要添加:
dataContext.TrackInfos.InsertOnSubmit(i);
Add(i);
我有以下四个方法重载:
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));
}
首先,我不能使用字符串作为约束,因为它是密封的。其次,我不认为我可以通过这种方式指定多个约束。有什么解决办法吗?
由于这部分 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;
}
您可以通过在曲目信息中添加隐式转换来避免常量构造 class。
public static implicit operator TrackInfo(string s) { return 新曲目信息; }
从 IEnumerable
不是最佳答案,但我认为这可能是您想要的:
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 和字符串是不可能的,因为它们都是 "concrete" 类型(classes),它们之间没有可能的继承层次结构(System.String class 是密封的)所以没有类型可以从两个 classes.
继承这段代码对我来说看起来不错。您可以通过简单地从方法调用中的字符串构造 TrackInfo 来从 IEnumerable<string>
重载调用 "primary overload"(采用单个 TrackInfo
的方法)来缩短调用堆栈(或者用第二个 Select 从每个字符串构造 TrackInfo)。
一种气味; Select 中使用的 lambda 表达式通常不应产生副作用;很明显,委托中使用的 Add() 方法除了对输入元素执行 return a "projection" 之外的操作,但对于样式,我仍然更喜欢将投影和添加分开的代码元素,即使它最终变得更加冗长。
受到 Wery Nguyen 的启发,并在我的 class 中进行了全面的通用重构。我最初发布的片段并不是全部问题,但我认为它可以解释我的意思。我应该把整个事情都贴出来。
我想到了这个:
#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 的基本方法。它们是:
- adding/removing 单品,
- adding/removing 多项,
- adding/removing 创建 DataContext 的多个项目。
每个单独的操作都由通过调用链传播的 Action 定义。 删除:
dataContext.TrackInfos.Attach(i);
dataContext.TrackInfos.DeleteOnSubmit(i);
Remove(i);
要添加:
dataContext.TrackInfos.InsertOnSubmit(i);
Add(i);