I have a Configuration class that has a number of settings in it. A simple example:
class Configuration
{
public string Name { get; set; }
public string Url { get; set; }
public string Password { get; set; }
//There are about 20 of these
}
I would like to offer the caller the ability to populate this object from a string dictionary, like so:
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.");
}
}
This works well, except it's an all-or-nothing conversion. If the caller provides a dictionary that is mostly good but one of the entries is misspelled, the conversion fails with an exception.
I'd like to be able to provide a better exception message that tells the caller which key was not found. Seems sort of important. But I am unable to retrieve that information from the KeyNotFoundException.
I could write code to parse the dictionary one line at a time and check for each key individually, but that seems like a real pain. Is there any way to tell which key was not found from the exception information or without searching for keys one line at a time with ContainsKey or TryGetValue?
Unfortunately the exception 'KeyNotFoundException' thrown from indexer in Dictionary<,> doesn't provide the 'key' value in error message.
Below is the generic extension method you can use to get detailed exception. Also in your code in the catch block you are swallowing the exception. Instead put it in as InnerException so it gets logged properly.
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.");
}
}
}
You can store the keys along with their mappings in a dictionary, and validate the input before mapping the values:
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;
});
}
The way I have solved this problem in the past is to wrap the real dictionary:
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);
}
}
...
}
...
}
Unfortunately the built-in exception simply doesn't provide the information.
My solution so far is to use an extension method:
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));
}
}
And use it like this:
return new Configuration
{
Name = dict.ParseFor<string>("Name"),
Url = dict.ParseFor<string>("Url"),
Password = dict.PasreFor<string>("Password")
}
The extension method will throw a helpful exception containing the key name. Also has the benefit of supporting type conversion, e.g. if one of the configuration items is a bool it'll attempt to convert it.
Use the "TryGetValue" method of the dictionary, and ditch the catch handler. The result of TryGetValue will be false if the key is not found, or true if it is. There is an "out" paremeter where the value will be, if true is returned. In lieu of your catch handler, you only have to keep track of which keys are not present (again indicated by the return value of false from TryGetValue).
Quick example:
string name;
Configuration config = new Configuration();
if (dict.TryGetValue("Name", out name))
{
config.Name = name;
}
else
{
throw new ArgumentException("'Name' was not present.");
}
EDIT:
In light of the clarification made to the question, I'm going to revise my answer to: No, it is not possible to get that information from the exception.
You can write a builder which will find properties of type 'string' that exist in your Configuration type, and then loop through these properties trying to fetch each one from given dictionary. This code even can be generic:
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;
}
Usage:
// throws FooException if any key is missing
var config = Something.Build<Configuration>(dict);
But usually, configurations do not contain strings.
What if Url is not valid uri?
What if Name is empty?
What if Password is null in dictionary?
What if some numeric setting is not a parsable string? Etc.
In all these cases your code will be happy and you'll get an exception at runtime. Things become much better if you use specific types for your settings string, Uri, int, DateTime etc. And validated these values as soon as you can. Also usually some settings are optional - you should not throw if this setting is missing in dictionary or whatever source you are using.
public class Configuration
{
public string Name { get; set; }
public Uri Url { get; set; }
public int RequiredNumber { get; set; }
public int? NotRequiredNumber { get; set; }
}
Then you can create set of extension methods which try to get and parse values from dictionary:
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;
}
And creating configuration will look like
var config = new Configuration {
Name = dict.GetString("Name"),
Url = dict.GetUri("Uri"),
RequiredNumber = dict.GetInt("RequiredNumber"),
NotRequiredNumber = dict.GetNullableInt("NotRequiredNumber")
};
Your code will fail fast if something is misconfigured. And you will be pretty safe if config is created.
Here what you can do.
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;
}
}
Related
I am using Unity with Parse Server and now wants to be able to link a user with OAuth.
This is what I am trying so far, but with no luck.
[System.Serializable]
public class DGUOAuth
{
public string dguId = "";
public string access_token = "";
}
DGUOAuth auth = new DGUOAuth()
{
access_token = "F12w06Ddqx1k5qj75JQWRZmzh16Zgf05wHExNnHAnh8",
dguId = "25-1999"
};
System.Threading.CancellationToken canceltoken;
Dictionary<string, object> data = new Dictionary<string, object>();
data.Add("authData", auth);
Debug.Log("Ready for linking the user!");
DataManager.user.LinkWithAsync("DGU", data, canceltoken).ContinueWith(t =>
{
if (t.IsFaulted)
{
Debug.Log("Faulted: " + t.Exception);
// Errors from Parse Cloud and network interactions
using (IEnumerator<Exception> enumerator = t.Exception.InnerExceptions.GetEnumerator())
{
if (enumerator.MoveNext())
{
ParseException error = (ParseException)enumerator.Current;
Debug.Log(error.Message);
// error.Message will contain an error message
// error.Code will return "OtherCause"
}
}
}
else
{
Debug.Log("User is linked");
}
});
Nothing happens and I only get "Ready for linking the user!" but no logs after that?! The documentation for LinkWithAsync and Unity is almost not existing...
Really hope someone can help me on this. Any help is appreciated and thanks in advance :-)
----- EDIT -----
Now, added a Debug.Log just after t.isFaulted and get this log:
System.AggregateException: One or more errors occurred. ---> System.ArgumentException: Unable to encode objects of type DGUOAuth
Not sure how to solve this. I do not get any error logs in the Parse server logs.
Issue
The error is basically coming from JsonUtility.Encode
public static string Encode(IDictionary<string, object> dict)
{
if (dict == null)
throw new ArgumentNullException();
if (dict.Count == 0)
return "{}";
StringBuilder builder = new StringBuilder("{");
foreach (KeyValuePair<string, object> pair in dict)
{
builder.Append(Encode(pair.Key));
builder.Append(":");
builder.Append(Encode(pair.Value));
builder.Append(",");
}
builder[builder.Length - 1] = '}';
return builder.ToString();
}
where builder.Append(Encode(pair.Value)); then tries to call Encode(object)
public static string Encode(object obj)
{
if (obj is IDictionary<string, object> dict)
return Encode(dict);
if (obj is IList<object> list)
return Encode(list);
if (obj is string str)
{
str = escapePattern.Replace(str, m =>
{
switch (m.Value[0])
{
case '\\':
return "\\\\";
case '\"':
return "\\\"";
case '\b':
return "\\b";
case '\f':
return "\\f";
case '\n':
return "\\n";
case '\r':
return "\\r";
case '\t':
return "\\t";
default:
return "\\u" + ((ushort) m.Value[0]).ToString("x4");
}
});
return "\"" + str + "\"";
}
if (obj is null)
return "null";
if (obj is bool)
return (bool) obj ? "true" : "false";
if (!obj.GetType().GetTypeInfo().IsPrimitive)
throw new ArgumentException("Unable to encode objects of type " + obj.GetType());
return Convert.ToString(obj, CultureInfo.InvariantCulture);
}
so without knowing that thing at all it looks like it simply expects an IDictionary<string, object> where the value can only be of the types
IDictionary<string, object> (where the value type again underlies the same type restrictions)
IList<object> (where the element type again underlies the same restrictions)
string
bool
primitive types (int, short, ulong, float, etc)
your given class DGUOAuth is neither of these => ArgumentException.
Solution(s)
So I would simply not use your DGUOAuth class at all but simply construct according dictionary either directly as
var data = new Dictionary<string, object>
{
{"authData" , "{\"access_token\" : \"F12w06Ddqx1k5qj75JQWRZmzh16Zgf05wHExNnHAnh8\", \"dguId\" : \"25-1999\"}"}
};
or if you want to as
var data = new Dictionary<string, object>
{
{"authData" , new Dictionary<string, object>
{
{"access_token", "F12w06Ddqx1k5qj75JQWRZmzh16Zgf05wHExNnHAnh8"},
{"dguId", "25-1999"}
}
}
};
where of course you can fill in the values also dynamically from variables if needed.
The alternative would be to make sure that your DGUOAuth returns such a dictionary this makes it maybe easier for you if you need to pass this around a lot or configure it via the Inspector
[System.Serializable]
public class DGUOAuth
{
public string dguId = "";
public string access_token = "";
public DGUOauth(string id, string token)
{
dguId = id;
access_token = token;
}
public IDictionary<string, object> ToDictionary()
{
return new Dictionary<string, object>
{
{"access_token", access_token},
{"dguId", dguId}
};
}
}
and then use it like
var data = new Dictionary<string, object>
{
{"authData", new DGUOauth("25-1999", "F12w06Ddqx1k5qj75JQWRZmzh16Zgf05wHExNnHAnh8").ToDictionary()}
};
or actually implements according interface IDictionary<string, object> which in my eyes is a lot of overkill for this little task.
I'm not even sure if you need that field name authData there or if it rather simply would expect
var data = new Dictionary<string, object>
{
{"access_token", "F12w06Ddqx1k5qj75JQWRZmzh16Zgf05wHExNnHAnh8"},
{"dguId", "25-1999"}
};
but that's something you will have to try.
I have the following code:
public class DiagnosticsSettings : Dictionary<string, string>
{
public string GetStringValue(string settingName)
{
return TryGetValue(settingName, out var result) ? result : throw new UnknownDiagnosticSettingException($"Unable to locate setting with name {settingName}.");
}
public T GetEnumValue<T>(string settingName)
{
var stringValue = GetStringValue(settingName);
if (string.IsNullOrWhiteSpace(stringValue))
throw new UnknownDiagnosticSettingException($"Unable to locate setting with name { settingName }");
return (T)Enum.Parse(typeof(T), stringValue);
}
I'm trying to unit test and have tried as follows:
[Test]
public void Test()
{
var settings = new DiagnosticsSettings();
var value = settings.GetEnumValue<CreatedOn>(CreatedOn.Today.ToString());
Assert.AreEqual(1, value);
}
My Enum class looks like this:
public enum CreatedOn
{
Today = 1,
All = 2
}
However I keep getting the error: "Unable to locate setting with name Today".
Do I need to pass in settings to the unit test to get this to pass?
Any guidance would be really appreciated, thanks
You aren't adding any KeyVlauePairs into the dictionary, so GetStringValue will always be null.
See this example, which should work:
[Test]
public void Test()
{
var settings = new DiagnosticsSettings { ["Today"] = "SomeValue" };
var value = settings.GetEnumValue<CreatedOn>(CreatedOn.Today.ToString());
Assert.AreEqual(1, value);
}
On another note, inheriting from a class such as Dictionary is rarely good practice. It provides a lot of behaviour, most of which you probably don't want to be exposing to consumers.
A better approach may be to use composition:
public class DiagnosticsSettings
{
private Dictionary<string, string> _dict = new Dictionary<string, string>();
public string GetStringValue(string settingName)
{
return _dict.TryGetValue(settingName, out var result) ? result : throw new UnknownDiagnosticSettingException($"Unable to locate setting with name {settingName}.");
}
public T GetEnumValue<T>(string settingName)
{
var stringValue = GetStringValue(settingName);
if (string.IsNullOrWhiteSpace(stringValue))
throw new UnknownDiagnosticSettingException($"Unable to locate setting with name { settingName }");
return (T)Enum.Parse(typeof(T), stringValue);
}
}
Which would only expose the desired behaviour.
If you want to retain the dictionary initialiser, you will also need to implement IEnumerable and Add(string, string):
public class DiagnosticsSettings : IEnumerable
{
// ...
public void Add(string arg1, string arg2)
{
_dict.Add(arg1, arg2);
}
IEnumerator IEnumerable.GetEnumerator()
{
return _dict.GetEnumerator();
}
}
The input data is shown below:
As you can see, at 1/1/2017, injector I1 is connected to four wellbores with some factors.. At date 1/2/2017, I1 is connected to three wellbores with respective factors.
I need to get pattern factor values at a particular date. For example 1/1/2017,
when the user mentions I1 and P2 for example.
The way I thought it might work is by using Dictionaries such that First dictionary as:
key: Date
value: Dictionary<injector,list<wellbores>>
and another dictionary as:
key: Date
value: Dictionary<injector,list<factors>>
How do I populate the respective dictionaries and access data for let's say
date: 1/1/2017 and injector - wellbore combination of i1-p2 .
Am i using the right approach?
The table structure or input data is how it is presented. There is nothing wrong with it.
If you can fetch the data as a list and the user inputs Injector and Wellbore value, why don't you just use LINQ to get your desired object. The way I would have implemented it as -
public class Data {
public string Injector {get;set;}
public string Wellbore {get;set;}
public DateTime Date {get;set;}
public double Factor {get;set;}
}
Then load the items into the list (Using NHibernate or EntityFramework or plain old ADO.Net wrapper class to read DataTable and load into a list.) -
var itemList = .....load items from database in list of type `List<Data>`.
If user supplied value is I1 and P1, then -
var item = itemList.FirstOrDefault(x => x.Injector == I1 && x.Wellbore == P1);
Now item.Factor is the factor I am looking for.
I would use Dictionary
but you can read your values by your key
I would give a generic example
Use would be like this
you can use what ever type
int myValue1 = GetValue<int>("your key", your Dictionary);
bool myValue2 = GetValue<bool>("your key", your Dictionary);
string myValue3 = GetValue("your key", your Dictionary);
public static T GetValue<T>(String paramName, Dictionary<string, string> param = null) where T : new()
{
Type myTypeObj = null;
T MyT = new T();
if (MyT == null)
{
myTypeObj = typeof(T);
myTypeObj = myTypeObj.GetProperty("Value").PropertyType;
}
else
{
myTypeObj = MyT.GetType();
}
MethodInfo myMethodInfo = myTypeObj.GetMethod("Parse", new[] { typeof(string) });
string value = "";
if (param.ContainsKey(paramName))
{
value = param[paramName];
if (!string.IsNullOrEmpty(value))
{
object[] reflParm = { value };
return (T)myMethodInfo.Invoke(MyT, reflParm);
}
}
return default(T);
}
public static String GetValue(String paramName, Dictionary<string, string> param = null)
{
string value = "";
if (param.ContainsKey(paramName))
{
value = param[paramName];
if (!string.IsNullOrEmpty(value))
return value;
}
return String.Empty;
}
in your case you should return your list by key and then you will get your list then you can do whatever you want with it
I hope that i could help
public class Table<T> where T:SomeClassWithIntegerID
{
private Dictionary<int, T> map = new Dictionary<int, T>();
public bool isInMemory(int id)
{
if (map.ContainsKey(id))
return true;
return false;
}
public T setIt(T obj)
{
map[obj.id] = obj;
}
public T getIt(int id)
{
return map[id];
}
}
Example:
private static Table<User> table = new Table<User>;
class User : SomeClassWithIntegerID
{
public string name { get; set; }
public string password { get; set; }
}
class SomeClassWithIntegerID
{
public int id { get; set; }
}
I can now check if the Table holds a user with a certain ID, because I use that as the key, but there is now no way for me to check if the Table holds a User with the name Bob or whatever. I want to be able to do something like table.isInMemory(name, "bob") but how is that possible with a generic type?
I need to create a function that allows the end user to specify the field and expected value of said field, after which Table will go over all objects of that class, stored in the Dictionary, to see if one has the field that matches that value.
Is this possible at all?
public bool IsInMemory(Func<T, bool> predicate)
{
return map.Values.Any(predicate);
}
You can then call it as:
table.IsInMemory(u => u.Name == "bob");
If you want to use a property name and value to match on you could add an overload:
public bool IsInMemory(string propertyName, object value)
{
PropertyInfo property = typeof(T).GetProperty(propertyName);
if(property == null) throw new ArgumentException("Invalid property name: " + propertyName);
var predicate = new Func<T, bool>(item => object.Equals(value, property.GetValue(item, null)));
return IsInMemory(predicate);
}
I would complement Lee's answer with a Where-method to enable querying with LINQ:
public IEnumerable<T> Where(Func<T, bool> predicate)
{
return map.Values.Where(predicate);
}
And an example:
table.Where(x => x.name.Contains("natli"))
.OrderBy(x => x.name);
To answer your actual question, you can (if you're using .NET 4.0) use the dynamic type, which resolves all methods and such at runtime, to call methods or properties that the compiler doesn't know about from its context.
dynamic dynObject = someObject;
dynObject.SomeMethod("Hi there", 27); // Call whatever method or property you "know" exist
I am trying to use the AS keyword with an unknown type. Here is my code:
public GetData(Type MyType, string CSVPath)
{
var engine = new FileHelperEngine(MyType);
try
{
_Data = engine.ReadFile(CSVPath) as MyType; //error here
}
catch(Exception ex)
{
Console.WriteLine("Error occured: " + ex.Message);
}
}
as you can see in this code I am getting an error were MyType is. Is there a better way to do this
Use a generic method instead of passing in a Type as a parameter:
public void GetData<T>(string CSVPath)
{
var engine = new FileHelperEngine(typeof(T));
_Data = engine.ReadFile(CSVPath) as T;
if (_Data != null)
{
//correct type, do the rest of your stuff here
}
}
I'm not sure I understand. First, using as doesn't throw an exception, it just returns null.
Second, I'm pretty sure you don't want to cast, you just want to check the type, so you need the is operator. But since MyType is only known at runtime, you indeed need reflection. It's quite simple:
object o = engine.Readfile(CSVPath);
if(MyType.IsAssignableFrom(o.GetType())
_Data = o;
else
Console.WriteLine("Mismatching types: {0} is not of type {1}", o.GetType(), MyType);
Note: I'm assuming _Data is of type object, otherwise, you just use the as operator with _Data's type.
Here's a class that does this, though I have a hard time thinking of a good case for a dynamic cast like this.:
using System;
namespace Test
{
class Program
{
private object _data;
static void Main(string[] args)
{
new Program().EntryPoint();
}
public void EntryPoint()
{
GetData(typeof(string), "Test");
Console.WriteLine(_data);
}
public void GetData(Type myType, string csvPath)
{
var engine = new FileHelperEngine(myType, csvPath);
// This is the line that does it.
_data = Convert.ChangeType(engine.ReadFile(csvPath), myType);
}
private class FileHelperEngine
{
public string Value { get; set; }
public FileHelperEngine(Type t, string value) { Value = value.ToString(); }
public string ReadFile(string path) { return Value; }
}
}
}