C# 在通用方法中将值强制转换为T
我有一个界面,用于制作吱吱作响的财产地图:C# 在通用方法中将值强制转换为T,c#,generics,C#,Generics,我有一个界面,用于制作吱吱作响的财产地图: 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 defaultVa
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..
}
}
public static T GetOrDefault(此IPropertyMap映射,字符串键,T defaultValue)
{
如果(!map.存在(键))
返回默认值;
其他的
{
if(typeof(T)=typeof(int))返回(T)map.GetInt(key);
//等等。。
}
}
但是编译器不允许我转换到t。我尝试添加where t:struct
,但似乎没有帮助
我缺少什么?我认为这是因为编译器不知道它需要执行什么类型的操作。IIRC,如果你引入拳击,你可以让它发挥作用:
if (typeof(T) == typeof(int)) return (T)(object)map.GetInt(key);
但就性能而言,这并不理想
不幸的是,我认为这只是泛型的一个限制。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;
}
我想这只是一个输入错误,但是
boolgetint(字符串键)
看起来很奇怪。它应该是intgetint(字符串键)
,或者更好的是intgetint32(字符串键)
接下来,Jon已经注意到,装箱是代码工作所必需的,所以这就是您要做的
最后,在IPropertyMap
界面中添加一个“一网打尽”的方法——比如说object GetValue(string key)
,然后重写GetOrDefault
,以使用此方法,而不是无休止且容易出错的类型比较:
else
return (T)(object)map.GetValue(key);
仅供参考,我发现了另一个接口,它有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;
}
publicstatict GetOrDefault(此IInfosContainer容器,字符串键,T defaultValue)
{
//我刚刚读了《深度》第73页,+1乔恩·斯凯特:)
如果(container==null)抛出新的ArgumentNullException(“container”);
if(容器存在(键))
{
if(container.GetType(key)!=typeof(T))
抛出新ArgumentOutOfRangeException(“键”,
“键存在,但与defaultValue参数的类型不同”);
其他的
return(T)container.GetAsObject(key);
}
其他的
返回默认值;
}
(纯粹主义者会注意到我不属于“一句话的支架”学派…我认为这不是一个好方法。你无法控制T是什么。比如说
浮点值=map.GetOrDefault(“blah”,2.0)
也不会编译,因为
无法将类型“double”隐式转换为“float”。存在显式转换(是否缺少强制转换?)
另一个失败点是所需的默认值为null时。这种方法让开发人员任由编译器来决定它认为开发人员打算做什么
如果可以更改接口,则添加一个GetObject方法。因为您使用的是扩展方法,所以我假设您不能将其强制转换为对象,然后再转换为int
公共静态void GetOrDefault(此IPropertyMap映射、字符串键、引用T值)
{
如果(映射存在(键))
{
if(typeof(T)=typeof(int))
{
value=(T)(对象)map.GetInt(键);
}
value=default(T);//这只是一个细节,因为我很懒,
//在这里添加真正的代码。
}
}
像这样打电话
PropertyMap map = new PropertyMap();
float value = 2.0f;
map.GetOrDefault("blah", ref value);
我讨厌裁判参数,但我明白这一点。注册表是这类方法有用的典型示例。上面的代码强制开发人员用户明确指定输出的类型,并保留概念默认值。我不喜欢双重强制转换的外观。所以我提出了这个解决方案:
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;
}
publictget(Guid id),其中T:ienty
{
目标价值;
value=Activator.CreateInstance(typeof(T))开关
{
Vehicle=>database.GetVehicles().FirstOrDefault(x=>x.Id==Id),
UserAccount=>database.GetUserAccounts().FirstOrDefault(x=>x.Id==Id),
_=>抛出新的NotImplementedException(“类型:+typeof(T).ToString()无数据),
};
返回(T)值;
}
如果您不熟悉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;
}
publictget(Guid id),其中T:ienty
{
目标价值;
开关(Activator.CreateInstance(typeof(T)))
{
案例车辆:
value=database.GetVehicles().FirstOrDefault(x=>x.Id==Id);
打破
案例用户帐户:
value=database.GetUserAccounts().FirstOrDefault(x=>x.Id==Id);
打破
违约:
抛出新的NotImplementedException(“类型:+typeof(T).ToString()没有数据);
}
返回(T)值;
}
附言:
如果使用其中T:IEntity
可以更改对象值代码>到亮度值代码>我猜这是一个输入错误,但是你界面中的所有方法都返回bool…?是的,复制粘贴是我的危险朋友…嗯,我不知道,因为我无法访问接口的所有实现。使用ChangeType(返回object)与按照Jon的建议进行装箱有什么区别吗?请参见编辑-但如果您不控制接口,它不是一个选项,我想…我不会在这个问题上循环,所以我怀疑性能是否会有问题。我刚刚对GetOrDefault和GetInt做了一个小测试,差别相当大,对于一百万次迭代,但对于我当前的需求仍然可以忽略。很好,对于我提到的接口不起作用,但我刚刚发现了另一个具有GetAsObject()的接口。是的,我已经发现了空问题,目前我只对结构使用它。不过,我相信没有
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;
}