我有一个creaky属性地图的界面:
interface IPropertyMap
{
bool Exists(string key);
int GetInt(string key);
string GetString(string key);
//etc..
}
我想创建一个像这样的扩展方法:
public static T GetOrDefault<T>(this IPropertyMap map, string key, T defaultValue)
{
if (!map.Exists(key))
return defaultValue;
else
{
if (typeof(T) == typeof(int)) return (T)map.GetInt(key);
//etc..
}
}
但编译器不会让我转向T.我尝试添加where T : struct
但这似乎没有帮助。
我错过了什么?
答案 0 :(得分:28)
我相信这是因为编译器不知道它需要执行什么类型的操作。 IIRC,如果你介绍拳击,你可以让它工作:
if (typeof(T) == typeof(int)) return (T)(object)map.GetInt(key);
但就性能而言,这并不理想。
不幸的是,我认为这只是对仿制药的限制。
答案 1 :(得分:3)
GetInt
,GetString
等内部做了什么?可能有其他选项涉及Convert.ChangeType(...)
或TypeDescriptor.GetConverter(...).ConvertFrom(...)
,以及使用“对象”索引器的单个演员:
例如,如果对象已经正确输入:
public T GetOrDefault<T>(this IPropertyMap map, string key, T defaultValue)
{
return map.Exists(key) ? (T)map[key] : defaultValue;
}
或者如果它们存储为字符串并需要转换,则涉及:
T typedVal = (T) TypeDescriptor.GetConverter(typeof(T)).ConvertFrom(map[key]);
答案 2 :(得分:3)
我想这只是一个错字,但bool GetInt(string key)
似乎很奇怪。它应该是int GetInt(string key)
,或者更好int GetInt32(string key)
。
接下来,Jon已经注意到你的代码需要装箱才能工作,所以这就是你所做的。
最后,在IPropertyMap
界面添加“全能”方法 - 比如object GetValue(string key)
,然后重写GetOrDefault<T>
以使用此方法,而不是无休止且容易出错{{ 1}}比较:
Type
答案 3 :(得分:0)
仅供参考,我发现了另一个具有GetType()和GetAsObject()方法的接口,它允许我集成这些答案的元素来执行此操作:
public static T GetOrDefault<T>(this IInfosContainer container, string key, T defaultValue)
{
//I just read p273 of C# in Depth, +1 Jon Skeet :)
if (container == null) throw new ArgumentNullException("container");
if (container.Exist(key))
{
if (container.GetType(key) != typeof(T))
throw new ArgumentOutOfRangeException("key",
"Key exists, but not same type as defaultValue parameter");
else
return (T)container.GetAsObject(key);
}
else
return defaultValue;
}
(纯粹主义者会注意到我不是'一个声明'学校的'大括号...... )
答案 4 :(得分:0)
我认为这不是一个好方法。你无法控制T是什么。例如
浮动值= map.GetOrDefault(“blah”,2.0);
不会编译,因为 无法将类型'double'隐式转换为'float'。存在显式转换(您是否错过了演员?) 另一个失败点是所需的默认值为null。这种方法使开发人员受编译器的支配,以解决它认为开发人员想要的内容。
如果您可以更改界面,则添加GetObject方法。由于您使用的是扩展方法,因此我假设您不能将对象强制转换为int。无论哪种方式都将方法更改为
public static void GetOrDefault(此IPropertyMap映射,字符串键,ref T值) { if(map.Exists(key)) { if(typeof(T)== typeof(int)) { value =(T)(object)map.GetInt(key); } value = default(T); //这只是一个很好的因为我很懒, //在这里添加真实代码 } } 并像这样打电话
PropertyMap map = new PropertyMap();
float value = 2.0f;
map.GetOrDefault("blah", ref value);
我讨厌ref params,但我明白这一点。注册表是这种方法何时有用的经典示例。上面的代码强制dev用户明确指定输出的类型并保留概念默认值。
答案 5 :(得分:0)
我不喜欢双重演员的样子。所以我想出了这个解决方案:
public T Get<T>(Guid id) where T : IEntity
{
object value;
value = Activator.CreateInstance(typeof(T)) switch
{
Vehicle => database.GetVehicles().FirstOrDefault(x => x.Id == id),
UserAccount => database.GetUserAccounts().FirstOrDefault(x => x.Id == id),
_ => throw new NotImplementedException("No data for type: " + typeof(T).ToString()),
};
return (T)value;
}
如果您不熟悉 switch 表达式,以下是使用 switch 语句的示例:
public T Get<T>(Guid id) where T : IEntity
{
object value;
switch(Activator.CreateInstance(typeof(T)))
{
case Vehicle:
value = database.GetVehicles().FirstOrDefault(x => x.Id == id);
break;
case UserAccount:
value = database.GetUserAccounts().FirstOrDefault(x => x.Id == id);
break;
default:
throw new NotImplementedException("No data for type: " + typeof(T).ToString());
}
return (T)value;
}
附注:
如果您使用 where T : IEntity
,您可以将 object value;
更改为 IEntity value;