多年来,我多次需要代码:
在字典中查找值;如果它不存在,则将其添加到字典中(并返回该新值)。
例如:
// Only one per account, so loading can be efficiently managed.
// <AccountID, LCProfilePicture>
public readonly static Dictionary<int, LCProfilePicture> All = new Dictionary<int, LCProfilePicture>();
public static LCProfilePicture GetOrCreate( int accountID )
{
LCProfilePicture pic;
if (!All.TryGetValue( accountID, out pic )) {
pic = new LCProfilePicture( accountID );
All[ accountID ] = pic;
}
return pic;
}
我不想每次都写那个样板文件,而是希望有一个通用的方法来完成工作。怎么在c#中这样做?
到目前为止,我已经想到了三种方法:
如果字典尚未包含密钥对象,则将所需的构造包装到Action
(或Func
?)中。如有必要,请拨打电话。
要求 TValue
拥有该表单的构造函数,然后以某种方式将该需求描述为泛型方法的约束。
定义interface
必须满足的TValue
,并以某种方式使用该接口编写泛型方法。
我想我知道如何做#1,所以一旦我弄清楚细节,就会提交答案。 更新:我现在已经解决了这个问题,并且贴出来作为答案。
但也许#2可能吗?然后我可以添加该约束,并完成。
(#3看起来不太有希望;我提到完整性。)
答案 0 :(得分:3)
您可以组合new()
的约束和用于设置键的界面,如下所示:
interface IWithKey<T> {
public T Key { get; set; }
}
static class DictExtensions {
public static TVal GetorCreate<TKey,TVal>(this IDictionary<TKey,TVal> d, TKey key) where TVal : new(), IWithKey<TKey> {
TVal res;
if (!d.TryGetValue(key, out res)) {
res = new TVal();
res.Key = key;
d.Add(key, res);
}
return res;
}
}
由于GetorCreate
是一个扩展名,您可以按如下方式使用它:
static LCProfilePicture GetOrCreatePic( int accountID ) {
return All.GetOrCreateEntry(accountID);
}
答案 1 :(得分:1)
我在你的例子中注意到你有一个静态字典
// Only one per account, so loading can be efficiently managed. // <AccountID, LCProfilePicture> public readonly static Dictionary<int, LCProfilePicture> All = new Dictionary<int, LCProfilePicture>();
我对此的第一反应是,因为它是静态的,你是否需要它是线程安全的。如果答案是肯定的,也许,甚至没有,那么答案可能是,不要自己写,让微软去做。
System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue>
恰好有2个内置函数
TValue GetOrAdd(TKey key, TValue value)
TValue GetOrAdd(TKey key, Func<TKey, TValue> func)
所有这些都是以线程安全的方式完成的。 参数为Func的第二个可能是您正在寻找的答案。
如果您着手简化用法,我会认为反对将数据加载作为TValue
的一部分。这主要是基于我自己的人选择存储POCO(普通旧CLR对象),因为值是字典而不是具有状态和行为的对象。
我会改为移动&#34;加载/构建/反序列化&#34;行为到另一个服务和/或词典本身。
此示例创建一个从
继承的基类public abstract class SmartConcurrentDictionaryBase<TKey, TValue> :
System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue>
{
public TValue GetOrAdd(TKey key) { return GetOrAdd(key, LoadNewValue); }
protected abstract TValue LoadNewValue(TKey key);
}
public class LCProfilePictureDictionary : SmartConcurrentDictionaryBase<int, LCProfilePicture>
{
protected override LCProfilePicture(int accountID)
{
return new LCProfilePicture(accountID);
}
}
// use is
// var pic = All.GetOrAdd(accountID);
此示例更像是一个可重用的Dictionary对象,并将Func作为构造函数参数接收,但可以轻松更改为包含接口,其中接口上的某个函数与模式匹配。
public class SimpleConcurrentDictionary<TKey, TValue> :
System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue>
{
private readonly Func<TKey, TValue> _loadFunc;
public SimpleConcurrentDictionary(Func<TKey, TValue> loadFunc)
{
_loadFunc = loadFunc;
}
public TValue GetOrAdd(TKey key) { return GetOrAdd(key, _loadFunc); }
}
答案 2 :(得分:0)
解决方案#1(最常见):
public static TValue GetOrCreateEntry<TKey, TValue>( Dictionary<TKey, TValue> dict, TKey key, Func<TValue> creator )
{
TValue value;
if (!dict.TryGetValue( key, out value )) {
value = creator();
dict[ key ] = value;
}
return value;
}
使用示例:
static LCProfilePicture GetOrCreatePic( int accountID )
{
return GetOrCreateEntry<int, LCProfilePicture>( All, accountID, () => new LCProfilePicture( accountID ) );
}
解决方案#2(对于记住密钥的电视节目):
public static TValue GetOrCreateEntry<TKey, TValue>( Dictionary<TKey, TValue> dict, TKey key, Func<TKey, TValue> creator )
{
TValue value;
if (!dict.TryGetValue( key, out value )) {
value = creator(key);
dict[ key ] = value;
}
return value;
}
使用示例:
static LCProfilePicture GetOrCreatePic( int accountID )
{
return GetOrCreateEntry<int, LCProfilePicture>( All, accountID, key => new LCProfilePicture( key ) );
}
解决方案#1和解决方案2的比较:
解决方案#1更为通用 - 它甚至可以用于不需要了解密钥的电视节目。
解决方案#2是更清晰的风格,对于做的TValues保留对密钥的引用。
在适当的情况下,最好有两个理由#2:
static LCProfilePicture GetOrCreatePic( int accountID )
{
// OOPS, programmer has not set the key field to "accountID".
return GetOrCreateEntry<int, LCProfilePicture>( All, accountID, () => new LCProfilePicture() );
}
如果主程序员/架构师想要避免这种可能性,请省略解决方案#1,并仅提供#2。在这种情况下,尝试使用不会编译,因为没有匹配的构造函数。
//...
int keyA;
int keyB;
// OOPS, programmer referred to the wrong key the second time.
// Maybe copy/pasted code, and only changed it in the first location, not realizing it is used in two places.
var valA = GetOrCreateEntry<int, LCProfilePicture>( All, keyA, () => new LCProfilePicture( keyB) );
enter code here
答案 3 :(得分:0)
System.Reflection有一个ConstructorInfo
对象和一个GetConstructor
方法,可用于此目的。 ConstructorInfo.Invoke
会返回您用于创建ConstructorInfo
的类型的对象。如果你去反射路线,它看起来像这样(没有经过测试,但应该接近):
//using System.Reflection;
public static TValue GetOrCreateEntry<TKey, TValue>(Dictionary<TKey, TValue> dict, TKey key)
{
TValue value;
if (!dict.TryGetValue(key, out value))
{
// not in dictionary
ConstructorInfo ctor = typeof(TValue).GetConstructor(new Type[] { typeof(TKey) });
if (ctor != null)
{
// we have a constructor that matches the type you need
value = (TValue)ctor.Invoke(new object[] { key });
dict[key] = value;
return value;
}
else
throw new NotImplementedException(); // because the TValue type does not implement the constructor you anticipate
}
// we got it from dictionary, so just return it
return value;
}