在泛型方法中将值转换为T.

时间:2009-05-13 08:15:22

标签: c# generics

我有一个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但这似乎没有帮助。

我错过了什么?

6 个答案:

答案 0 :(得分:28)

我相信这是因为编译器不知道它需要执行什么类型的操作。 IIRC,如果你介绍拳击,你可以让它工作:

if (typeof(T) == typeof(int)) return (T)(object)map.GetInt(key);

但就性能而言,这并不理想。

不幸的是,我认为这只是对仿制药的限制。

答案 1 :(得分:3)

GetIntGetString等内部做了什么?可能有其他选项涉及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;