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