Как определить, какой ключ не найден? - PullRequest
0 голосов
/ 28 сентября 2018

У меня есть класс 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.

Я мог бы написать код для разбора словаря по одной строке за раз и проверки каждого ключа в отдельности, но это похоже на настоящую боль.Есть ли способ узнать, какой ключ не был найден, из информации об исключении или без поиска ключей по одной строке за раз с ContainsKey или TryGetValue?

Ответы [ 7 ]

0 голосов
/ 28 сентября 2018

Вы можете написать конструктор, который найдет свойства типа 'string', существующие в вашем типе Configuration, а затем перебрать эти свойства, пытаясь извлечь каждое из заданного словаря.Этот код даже может быть универсальным:

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 в словаре?
  • Что если некоторые числовые настройки не являются парсируемой строкой?И т.д.

Во всех этих случаях ваш код будет счастлив, и вы получите исключение во время выполнения.Все станет намного лучше, если вы будете использовать определенные типы для своих настроек string, Uri, int, DateTime и т. Д. И подтвердите эти значения, как только сможете.Также обычно некоторые параметры являются необязательными - вы не должны бросать, если этот параметр отсутствует в словаре или любом другом источнике, который вы используете.

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

Ваш код быстро потерпит неудачу в случае неправильной настройкиИ вы будете в полной безопасности, если будет создан конфиг.

0 голосов
/ 29 сентября 2018

Вот что вы можете сделать.

  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;
    }
}
0 голосов
/ 28 сентября 2018

Вы можете сохранить ключи вместе с их отображениями в словаре и проверить ввод перед отображением значений:

public static Configuration CreateFromDictionary(Dictionary<string, string> dict)
{
    var mappings = new Dictionary<string, Action<Configuration, string>>
    {
        [nameof(Name)] = (x, value) => x.Name = value,
        [nameof(Url)] = (x, value) => x.Url = value,
        [nameof(Password)] = (x, value) => x.Password = value,
    };

    var missingKeys = mappings.Keys
        .Except(dict.Keys)
        .ToArray();
    if (missingKeys.Any())
        throw new KeyNotFoundException("The given keys are missing: " + string.Join(", ", missingKeys));

    return mappings.Aggregate(new Configuration(), (config, mapping) =>
    {
        mapping.Value(config, dict[mapping.Key]);
        return config;
    });
}
0 голосов
/ 28 сентября 2018

К сожалению, исключение KeyNotFoundException, выброшенное из индексатора в Dictionary<,>, не предоставляет значение '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.");
        }
    }
}
0 голосов
/ 28 сентября 2018

Мое решение до сих пор состоит в том, чтобы использовать метод расширения:

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, он попытается преобразовать его.

0 голосов
/ 28 сентября 2018

Способ решения этой проблемы в прошлом - обернуть настоящий словарь:

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

К сожалению, встроенное исключение просто не предоставляет информацию.

0 голосов
/ 28 сентября 2018

Используйте метод «TryGetValue» из словаря и отключите обработчик catch.Результат TryGetValue будет ложным, если ключ не найден, или истинным, если он есть.Существует параметр «out», в котором будет находиться значение, если возвращается значение true.Вместо вашего обработчика перехвата, вам нужно только следить за тем, какие ключи отсутствуют (снова на это указывает возвращаемое значение false из TryGetValue).

Быстрый пример:

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

РЕДАКТИРОВАТЬ: В свете разъяснений, сделанных по этому вопросу, я собираюсь пересмотреть мой ответ: Нет, получить эту информацию из исключения невозможно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...