如何判断未找到哪个密钥?

时间:2018-09-28 18:52:08

标签: c# .net exception-handling

我有一个Configuration类,其中包含许多设置。一个简单的例子:

class Configuration
{
    public string Name { get; set; }
    public string Url { get; set; }
    public string Password { get; set; }
    //There are about 20 of these
}

我想为调用者提供从字符串字典填充此对象的功能,如下所示:

static Configuration CreateFromDictionary(Dictionary<string, string> dict)
{
    try
    {
        return new Configuration
        {
            Name     = dict["Name"],
            Url      = dict["Url"],
            Password = dict["Password"]
        }
    }
    catch(KeyNotFoundException exception)
    {
        throw new ArgumentException("Unable to construct a Configuration from the information given.");
    }
}

这很好,除了它是一个全有或全无的转换。如果调用方提供的字典大部分都很好,但是其中一个条目的拼写错误,则转换将失败,并出现异常。

我希望能够提供更好的异常消息,告知调用者哪个密钥未找到。似乎很重要。但是我无法从KeyNotFoundException检索该信息。

我可以编写代码来一次解析字典的每一行并分别检查每个键,但这似乎是很痛苦的。是否有任何方法可以判断是否从异常信息中找不到哪个密钥,或者不用ContainsKeyTryGetValue一次只搜索一行密钥?

7 个答案:

答案 0 :(得分:1)

不幸的是,Dictionary<,>中的索引器抛出的异常'KeyNotFoundException'没有在错误消息中提供'key'值。

以下是可用于获取详细异常的通用扩展方法。同样在catch代码中的代码中,您正在吞没异常。而是将其作为InnerException放入,以便正确记录。

static Configuration CreateFromDictionary(Dictionary<string, string> dict)
{
            try
    {
        return new Configuration
        {
            Name = dict.GetValue("Name"),
            Url = dict.GetValue("Url"),
            Password = dict.GetValue("Password")
        }
    }
    catch (KeyNotFoundException ex)
    {
        throw new ArgumentException("Unable to construct a Configuration from the information given.", ex);
    }
 }

public static class ExtensionsUtil
{
    public static Tvalue GetValue<Tvalue, TKey>(this Dictionary<TKey, Tvalue> dict, TKey key)
    {
        Tvalue val = default(Tvalue);
        if (dict.TryGetValue(key, out val))
        {
            return val;
        }
        else
        {
            throw new KeyNotFoundException($"'{key}' not found in the collection.");
        }
    }
}

答案 1 :(得分:1)

您可以将键及其映射存储在字典中,并在映射值之前验证输入:

gcloud projects create tf-admin-001 --name tf-admin --organization xxxx --set-as-default

答案 2 :(得分:0)

使用字典的“ TryGetValue”方法,并丢弃捕获处理程序。如果找不到密钥,则TryGetValue的结果将为false,否则将为true。如果返回true,则存在一个“出”参数。代替捕获处理程序,您只需跟踪不存在的键(再次由TryGetValue的false的返回值指示)。

简单示例:

string name;
Configuration config = new Configuration();
if (dict.TryGetValue("Name", out name))
{
    config.Name = name;
}
else
{
    throw new ArgumentException("'Name' was not present.");
}

编辑: 根据对该问题的说明,我将答案修改为:不,不可能从异常中获取该信息。

答案 3 :(得分:0)

我过去解决此问题的方法是包装真正的字典:

public class MyDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
    public MyDictionary(IDictionary<TKey, TValue> realDictionary)
    {
         _dictionary = realDictionary;
    }

    ...

    public TValue this[TKey key]
    {
        get
        {
            try
            {
                return _dictionary[key];
            }
            catch (KeyNotFoundException e)
            {
                throw new KeyNotFoundException($"Key {key} is not in the dictionary", e);
            }
        }
        ...
    }
    ...
}

不幸的是,内置异常根本不提供信息。

答案 4 :(得分:0)

到目前为止,我的解决方案是使用扩展方法:

public static T ParseFor<T>(this IDictionary<string, string> source, string key)
{
    string val;
    if (!source.TryGetValue(key, out val)) throw new KeyNotFoundException(string.Format("An entry for '{0}' was not found in the dictionary", key));
    try
    {
        return (T)Convert.ChangeType(val, typeof(T));
    }
    catch
    {
        throw new FormatException(string.Format("The value for '{0}' ('{1}') was not in a correct format to be converted to a {2}", key, val, typeof(T).Name));
    }
}

并像这样使用它:

return new Configuration
    {
        Name     = dict.ParseFor<string>("Name"),
        Url      = dict.ParseFor<string>("Url"),
        Password = dict.PasreFor<string>("Password")
    }

扩展方法将抛出一个有用的异常,其中包含密钥名称。还具有支持类型转换的好处,例如如果配置项之一是bool,它将尝试对其进行转换。

答案 5 :(得分:0)

您可以编写一个构建器,该构建器将找到您的Configuration类型中存在的'string'类型的属性,然后遍历这些属性,尝试从给定的字典中获取每个属性。该代码甚至可以是通用的:

public T Build<T>(Dictionary<string, string> source)
    where T : new()
{
    var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty;
    var properties = typeof(T).GetProperties(flags)
         .Where(p => p.PropertyType == typeof(string));

    var missingKeys = properties.Select(p => p.Name).Except(source.Keys);
    if (missingKeys.Any())
        throw new FooException($"These keys not found: {String.Join(", ", missingKeys)}");

    var obj = new T();

    foreach (var p in properties)
        p.SetValue(obj, source[p.Name]);

    return obj;
}

用法:

// throws FooException if any key  is missing
var config = Something.Build<Configuration>(dict); 

但是通常,配置不包含字符串。

  • 如果Url无效的uri怎么办?
  • 如果Name为空怎么办?
  • 如果Password在字典中是null怎么办?
  • 如果某些数字设置不是可分析的字符串怎么办?等等

在所有这些情况下,您的代码都会很高兴,并且您将在运行时遇到异常。如果您使用特定类型的设置stringUriintDateTime等,情况会好得多。并尽快验证这些值。通常,有些设置是可选的-如果字典或您使用的任何来源缺少此设置,则不要抛出该错误。

public class Configuration
{
    public string Name { get; set; }
    public Uri Url { get; set; }
    public int RequiredNumber { get; set; }
    public int? NotRequiredNumber { get; set; }
}

然后,您可以创建一组扩展方法,这些方法尝试从字典中获取和解析值:

public static Uri GetUri(this Dictionary<string, string> source, string key)
{
    if (!source.TryGetValue(key, out string value))
        throw new ConfigurationException($"{key} not found");

    if (!Uri.TryCreate(value, UriKind.Absolute, out Uri result))
        throw new ConfigurationException($"{key} is not a valid uri: {value}");

    return result;
}

创建配置看起来像

var config = new Configuration {
    Name = dict.GetString("Name"),
    Url = dict.GetUri("Uri"),
    RequiredNumber = dict.GetInt("RequiredNumber"),
    NotRequiredNumber = dict.GetNullableInt("NotRequiredNumber")
};

如果配置错误,您的代码将很快失败。创建配置后,您将非常安全。

答案 6 :(得分:0)

这是您可以做的。

  static Configuration CreateConfiguration(Dictionary<string,string> dict)
    {
        try
        {
            return
                new Configuration
                {
                    FirstName = dict.getValue("FirstName"),
                    LastName = dict.getValue("LastName"),
                    ID = dict.getValue("ID")                        
                };
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    public static class Extension
{
    public static string getValue(this Dictionary<string, String> dict, string keyName)
    {
        string data = null;
        var result = dict.TryGetValue(keyName, out data);
        if (!result)
            throw new KeyNotFoundException($"The given key '{keyName}' was not present in the dictionary. keyname is not found");
        return data;
    }
}