How to deserialize an immutable data structure? - c#

How would I deserialize YAML to a immutable data structure?
e.g. I have this YAML:
Value: SomeString
Number: 99
And this data structure:
public class MyData
{
public MyData(string value, int number)
{
Value = value;
Number = number;
}
public string Value { get; }
public int Number { get; }
}
For this I'd to use the constructor. So somehow I'd need to first retrieve a Dictionary<string, object> parsed from the YAML respecting my class (so 99 would be int, not string), then scan my type for an appropriate constructor,

Although the question doesn't mention it, I'm assuming you are using YamlDotNet (or SharpYaml which is a fork of YamlDotNet)
YamlDotNet doesnt support deserializing into classes that do not have a default constructor - but one option to achieve what you want is to deserialize into an intermediate Builder type that is mutable which can produce the final type.
e.g.
public class MyDataBuilder
{
public string Value { get; set; }
public int Number { get; set; }
public MyData Build() => new MyData(Value, Number);
}
And then use something like:
deserializer.Deserialize<MyDataBuilder>(yaml).Build();
You would end up having to create a parallel set of builders for your whole model however, e.g. if MyData had a third parameter of type MyOtherData (I've changed the example to use records instead of classes to make it concise):
public record MyOtherData(string OtherValue);
public record MyData(string Value, int Number, MyOtherData otherData);
In which case we would need another Builder:
public class MyOtherDataBuilder
{
public string OtherValue { get; set; }
}
And MyDataBuilder would look like:
public class MyDataBuilder
{
public string Value { get; set; }
public int Number { get; set; }
public MyOtherDataBuilder MyOtherData { get; set; }
public MyData Build() => new MyData(Value, Number, MyOtherData.Build());
}

It's an old but surprisingly relevant question. Now, with records in C#, immutable collections in .net, lack of ability to deserialize immutable data is a blocker - there is no way we need to change all our data types just to be able to deserialize. One practical workaround that I found - is to convert yaml to json first, then deal with json your preferred way - System.Text.Json, Newtonsoft, etc.
Here is how to do is easiest way:
static string ConvertToJson(string yaml) {
object DeserializeYaml() =>
new DeserializerBuilder()
.Build()
.Deserialize(new StringReader(yaml))
?? throw new InvalidOperationException("Cannot deserialize yaml string:" + Environment.NewLine + yaml);
string SerializeYamlObjectToJson(object yamlObject) =>
new SerializerBuilder()
.JsonCompatible()
.Build()
.Serialize(yamlObject);
return SerializeYamlObjectToJson(DeserializeYaml());
}
The only disadvantage, potentially big, is performance. I feel, however, that it's rarely an important requirement for yaml.

use the FormatterServices.GetUninitializedObject API (this will NOT invoke any constructors at all) and then use reflection to set fields.
Code example:
var instance = FormatterServices.GetUninitializedObject(typeof(MyData));
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
var type = typeof(MyData);
var stringField = type.GetField("_value", flags);
stringField.SetValue(instance, "SomeString");
var numberField = type.GetField("_number", flags);
numberField.SetValue(instance, 99);
MyData data = (MyData)instance;

Related

Is it possible to optimize large switch statements in C#?

I am working on a websocket client application. The server send messages in JSON format and I want to deserialize it. There have one string in the JSON format data that shows the type of message (it has about 50 types today, maybe it will have more in the future).
So I have written a large switch statement like this:
switch(type){
case "type1":
DoSth<T1>(DeserializeFunction<T1>(message));
break;
case "type2":
DoSth<T2>(DeserializeFunction<T2>(message));
break;
//...
}
Is it possible to optimize this statement?
This is the model:
public record EventMessage<T> where T : IEventExtraBody
{
// this will always be 0
[JsonPropertyName("s")]
public int EventType { get; set; }
[JsonPropertyName("sn")]
public long SerialNumber { get; set; }
[JsonPropertyName("d")]
public EventMessageData<T> Data { get; set; }
public override string ToString()
{
return JsonSerializer.Serialize(this);
}
}
public record EventMessageData<T> where T : IEventExtraBody
{
// Some other properties
[JsonPropertyName("extra")]
public EventMessageExtra<T> Extra { get; set; }
}
public record EventMessageExtra<T> where T : IEventExtraBody
{
[JsonPropertyName("type")]
public string Type { get; set; } // this string indicates the type of message
[JsonPropertyName("body")]
public T Body { get; set; }
}
Body (an example):
public record ExitedGuildEvent : IEventExtraBody
{
[JsonPropertyName("user_id")]
public string UserId { get; set; }
[JsonPropertyName("exited_at")]
public long ExitedAt { get; set; }
}
When message arrived, I use JsonDocument to get the type string.
var typeString = JsonDocument.Parse(message.Text).RootElement.GetProperty("d").GetProperty("extra").GetProperty("type").GetString()
Then, I want to deserialize the message and publish it to MessageHub.
Deserializing the json string and publish:
_messageHub.Publish(JsonSerializer.Deserialize<EventMessage<BodyType>>(message.Text));
And because there are lots of BodyType, and EventMessage<Type.GetType("TypeClassPath")>(message.Text) is illegal, I write a large switch statement.
Maybe I have build a very bad model for this situation. I hope you can give me some advice.
You could replace switch-case with a hashmap. To do that you just need to move every case into separate function. Here you can create a factory method to help you to fill out a hashmap because cases are pretty similar
public class YourHub
{
private IMessageHub _messageHub = new MessageHub();
private Dictionary<string, Action<string, IMessageHub>> _methods;
public YourHub()
{
//fill out the hashmap for all types that you have
//make sure this hashmap is shared between operations
_methods = new Dictionary<string, Action<string, IMessageHub>>()
{
{"key1", CreateAction<EventMessage<ExitedGuildEvent>>() }
};
}
//factory method for the actions
private Action<string, IMessageHub> CreateAction<T>()
{
return (json, hub) => hub.Publish(JsonSerializer.Deserialize<T>(json, null));
}
public void ProcessMessage(string json)
{
var typeString = JsonDocument
.Parse(json)
.RootElement.GetProperty("d")
.GetProperty("extra")
.GetProperty("type")
.GetString();
if (!_methods.ContainsKey(typeString)) throw new NotSupportedException();
var method = _methods[typeString];
method(json, _messageHub);
}
}
This aproach won't give you a huge perfomance boost on 50 elements, but it looks cleaner. The runtime complexity is O(1) compared to O(n) with switch-case, but it takes O(n) additional space.
A better solution than a big switch would probably be to refactor DeserializeFunction into an interface and class.
Register It by type and then resolve it. Either with a DI container or by a dictionary where you map.
interface IMessageDeserializer {
object Deserialize(Message message);
}
class Type1Deserializer : IMessageDeserializer {
public object Deserialize(Message message){
// Implementation that returns a Type1
return new Type1(){
};
}
}
// Register your serializers (you can use also a DI container but this is simpler just to show how) in a dictionary, preferably reused
Dictionary<Type, IMessageDeserializer> serializers = new Dictionary<Type, IMessageDeserializer>();
serializers.Add("type1", new Type1Deserializer());
serializers.Add("type2", new Type2Deserializer());
serializers.Add("type3", new Type3Deserializer());
// When you need it, use it like this:
string type = "type1"; // This is from your other code
var message = GetMessage(); // This is from your other code
IMessageDeserializer serializer = serializers[type];
object deserializedMessage = serializer.Deserialize(message);
// To create your event message, either add a constraint to the T of IMessageDeserializer so you can pass it into another function that creates the event message or just simply return the messagehub message as json directly from your IMessageDeserializer implementation)
(I wrote this from memory so I apologise for any mistakes)

ServiceStack Redis Get an struct always return default

I'm using ServiceStack Redis. I have an struct and I want to storage it in Redis. But when I try to get it, it always return the default value. But if I change the struct for class it works ok. Any ideas?
public struct PersonStruct
{
public string Name { get; set; }
public int Year { get; set; }
}
Unit test (It always pass, because redis return the default for the struct)
var obj = new PersonStruct() {Name = "SomeName", Year = 1235};
_redis.Set(key, obj, TimeSpan.FromMinutes(2));
var result = _redis.Get<PersonStruct>(key);
Assert.AreEqual(default(PersonStruct), result);
For structs you have to use custom serializer/deserializer
var obj = new PersonStruct() {Name = "SomeName", Year = 1235};
var bytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(obj));
_redis.Set(key, bytes, TimeSpan.FromMinutes(2));
var getBytes = _redis.Get(key);
var result = JsonSerializer.Deserialize<PersonStruct>(Encoding.UTF8.GetString(getBytes));
Seems it's serializing structs by calling ToString()
The differences with Struct's is that they're treated like a single scalar value, where instead of serializing every property of a struct value, e.g. Date, Day, DayOfWeek, DayOfYear, Hour, etc in the case of a DateTime, only a single scalar value is serialized for DateTime's, TimeSpan's, etc.
ServiceStack.Text serializers does this by convention where it will attempt to use ToString() to serialize into a single string value and the TStruct(string) constructor to deserialize the value. Alternatively you can provide your own deserializer function with a static ParseJson() method.
So if you want to deserialize structs you would need to provide a serializer/deserializer for your Struct by overriding ToString() and providing a string constructor, e.g:
public struct PersonStruct
{
public PersonStruct(string jsonStr)
{
var str = jsonStr.FromJson<string>();
Year = int.Parse(str.LeftPart(','));
Name = str.RightPart(',');
}
public string Name { get; set; }
public int Year { get; set; }
public override string ToString() => Year + "," + Name;
}
Then you'll be able to use it in ServiceStack.Redis Typed APIs as seen in this Gistlyn example gist.

Deserializing JSON with multi-level nesting

Yet another mtgjson.com inspired question; none of the other, similar questions are getting me where I need to be. First, a couple lines of sample JSON (from mtgjson's AllPrices.json):
"00028782-6ec2-54fe-8633-2c906d8f1076": {"prices": {"mtgo": {}, "mtgoFoil": {}, "paper": {"2019-12-01": 0.15}, "paperFoil": {}}},
"00040b50-3b84-5cea-b663-70038b87fa08": {"prices": {"mtgo": {"2019-12-02": 0.02}, "mtgoFoil": {"2019-12-02": 0.02}, "paper": {"2019-12-01": 0.15}, "paperFoil": {"2019-12-01": 0.53}}}
Each parent object is a GUID and the Price Info; the Price Info is the four types of prices offered, and for each of those four types, the price data is Last Updated Date and Price.
The classes I've created (after lots of other approaches, all of which have failed):
public class price_Class
{
public string Updated { get; set; }
public decimal Price { get; set; }
}
public class PriceInfo
{
[JsonProperty("mtgo")] public price_Class mtgo { get; set; }
[JsonProperty("mtgoFoil")] public price_Class mtgof { get; set; }
[JsonProperty("paper")] public price_Class RegPrice { get; set; }
[JsonProperty("paperFoil")] public price_Class FoilPrice { get; set; }
}
And how I'm using it:
dynamic prices = JsonConvert.DeserializeObject(sJSON);
IDictionary<string, JToken> pricelist = prices;
foreach (var priceline in pricelist)
{
sUUID = priceline.Key.ToString();
PriceInfo pi = JsonConvert.DeserializeObject<PriceInfo>(priceline.Value.ToString());
Stepping through in debug mode, I see that prices seems fine; pricelist, also. The foreach defines priceline as I'd expect, and sUUID is correctly defined - but pi shows up with all four sets of price data as null - not just those that are null, but those that should have data.
priceline.value looks fine, to me:
{{
"mtgo": {},
"mtgoFoil": {},
"paper": {
"2019-12-01": 0.53
},
"paperFoil": {
"2019-12-01": 4.53
}
}}
When I expand pi in the Locals window, it shows the four classes (FoilPrice, RegPrice, mtgo, mtgof), but the contents are null.
What I need, in case it's not obvious, is to have pi.RegPrice and pi.FoilPrice defined, with a Date and Price, when that data actually exists in the JSON.
I'll admit, nested classes and JSON in general is still outside my comfort zone; I appreciate all help!
The price_Class is not adequate for deserialize your JSON object.
try with this:
public class PriceInfo
{
[JsonProperty("mtgo")] public Dictionary<string, decimal> mtgo { get; set; }
[JsonProperty("mtgoFoil")] public Dictionary<string, decimal> mtgof { get; set; }
[JsonProperty("paper")] public Dictionary<string, decimal> RegPrice { get; set; }
[JsonProperty("paperFoil")] public Dictionary<string, decimal> FoilPrice { get; set; }
}
using this tool you can find exactly what are your DTO
the problem are on your json data as i believe date are not send like this in json it come as array of integers and with specific order like day month year .
as well as you need to create your DTO similar to json even in types you can't map it till you set the same type of json in your DTO
public class Prices
{
public Mtgo mtgo { get; set; }
public MtgoFoil mtgoFoil { get; set; }
public Paper paper { get; set; }
public PaperFoil paperFoil { get; set; }
}
public class RootObject
{
public Prices prices { get; set; }
}
don't forget to tag all of them with [JsonProperty("json prop name ")]
Parsing and formatting utilities for JSON.
A central concept in lift-json library is Json AST which models the structure of a JSON document as a syntax tree.
sealed abstract class JValue
case object JNothing extends JValue // 'zero' for JValue
case object JNull extends JValue
case class JString(s: String) extends JValue
case class JDouble(num: Double) extends JValue
case class JInt(num: BigInt) extends JValue
case class JBool(value: Boolean) extends JValue
case class JField(name: String, value: JValue) extends JValue
case class JObject(obj: List[JField]) extends JValue
case class JArray(arr: List[JValue]) extends JValue
It comes with Lift, but non-Lift users can add lift-json as a dependency in following ways. Note, replace XXX with correct Lift version.
SBT users
Add dependency to your project description:
val lift_json = "net.liftweb" %% "lift-json" % "XXX"
Maven users
Add dependency to your pom:
<dependency>
<groupId>net.liftweb</groupId>
<artifactId>lift-json</artifactId>
<version>XXX</version>
</dependency>
Summary of the features:
Fast JSON parser
LINQ style queries
Case classes can be used to extract values from parsed JSON
Diff & merge
DSL to produce valid JSON
XPath like expressions and HOFs to manipulate JSON
Pretty and compact printing
XML conversions
Serialization
Low level pull parser API
Try using this for deeply nested JSONs.
It seems that the odd sub-structure of {Prices:{label:{date:amount}}} just doesn't work well with Newtonsoft's (otherwise excellent) JSON tools.
I tried the various tools (some suggested here) to generate classes; they were getting confused by the dates, creating classes for each date. I even tried generating classes for just the substring of data (priceline.value, in the example) - nope, still wouldn't work.
I ended going with a brute-force, string manipulation approach; it's ugly, I'm not exactly proud of it - but I now have what I needed. Here's the relevant snippets, just in case anyone else stumbles on the same things as I did:
private static string RemoveNoise(string input)
{
input = Regex.Replace(input, #"\r\n?|\n", string.Empty); // no more NewLine stuff
return input.Replace(" ", string.Empty)
.Replace(#"""",string.Empty);
}
...
public class PriceData
{
public string UUID { get; set; }
public string Updated { get; set; }
public string Price { get; set; }
public string FoilUpd { get; set; }
public string FoilPrc { get; set; }
}
...
string sPaperTag = #"PAPER:{";
string sPprFlTag = #"PAPERFOIL:{";
...
dynamic prices = JsonConvert.DeserializeObject(sJSON);
IDictionary pricelist = prices;
foreach (var priceline in pricelist)
{
PriceData pData = new PriceData();
pData.UUID = priceline.Key.ToString();
bool bWeHavePrice = false;
string pi = RemoveNoise(priceline.Value.ToString().ToUpper());
// parse out paper, paperFoil dates & prices manually (unusual JSON format...)
iBeg = pi.IndexOf(sPaperTag);
if (iBeg >= 0)
{
sTemp = pi.Substring(iBeg, pi.Length - iBeg);
iBeg = sTemp.IndexOf(":") + 2;
iEnd = sTemp.IndexOf("}");
sTemp = sTemp.Substring(iBeg, iEnd - iBeg); // either YYYY-MM-DD:n.nn, or an empty string
iBeg = sTemp.IndexOf(":");
if (iBeg > 0)
{
if (DateTime.TryParse(sTemp.Substring(0, iBeg), out dtTemp)) { pData.Updated = dtTemp.ToString(); bWeHavePrice = true; }
if (Decimal.TryParse(sTemp.Substring(++iBeg, sTemp.Length - iBeg), out decTemp)) { pData.Price = decTemp.ToString(); bWeHavePrice = true; }
}
}
I do that string manipulation dance again for the foil prices; I'm not currently interested in the 'mtgo' or 'mtgoFoil' data.
I'm doing all that TryParse stuff to make sure I have a valid date or amount, but I'm using the results to populate parameters in a SQLCommand, so I have to have strings; seems like extra work, going from string to Date or Decimal, then back to string - but this way I don't get exceptions when executing the SQL Insert command.
My thanks to all who helped, or tried to help. And if someone figures out how to handle it via JSON.Net, I'd love to see it!

Using ServiceStack OrmLite to create Key Value table for dynamic types

I want to create a key value table in my database along the lines of
public class KeyValue {
public string Id { get; set; }
public dynamic Value {get; set; }
}
Using a slightly modified SqlProvider I have no problems getting CreateTable<KeyValue>() to generate varchar(1024) Id, varchar(max) Value.
I have no issues saving objects to it. The problem is when I load the objects
var content = dbConn.GetById<KeyValue>("about");
content.Value at this point is a string.
Looking at the database record, the text for value does not appear to store any type information.
Is there really anything I can do better other than manually invoking ServiceStack.Text and call deserialize with the appropriate type information?
I do not need absolute dynamic, my actual use case is for polymorphism with a base class instead of dynamic. So I don't really care what type Value is whether it's the base class, dynamic, object, etc. Regardless other than using the class
public class KeyValue {
public string Id { get; set; }
public MySpecificChildType Value {get; set; }
}
I haven't been able to get anything other than a string back for Value. Can I tell OrmLite to serialize the type information to be able to correctly deserialize my objects or do I just have to do it manually?
Edit: some further information. OrmLite is using the Jsv serializer defined by ServiceStack.Text.TypeSerializer and is in no way pluggable in the BSD version. If I add a Type property to my KeyValue class with the dynamic Value I can do
var value = content.Value as string;
MySpecificChildType strongType =
TypeSerializer.DeserializeFromString(content, content.Type);
I just really want a better way to do this, I really don't like an object of 1 type going into the db coming back out with a different type (string).
I haven't worked much with the JsvSerializer but with the JsonSerializer you can achieve this (in a few different ways) and as of ServiceStack 4.0.11 you can opt to use the JsonSerializer instead, see https://github.com/ServiceStack/ServiceStack/blob/master/release-notes.md#v4011-release-notes.
Example
public abstract class BaseClass {
//Used for second example of custom type lookup
public abstract string Type { get; set; }
}
public class ChildA : BaseClass {
//Used for second example of custom type lookup
public override string Type { get; set; }
public string PropA { get; set; }
}
And then in your init/bootstrap class you can configure the serializer to emit the type information needed for proper deserialization:
public class Bootstrapper {
public void Init() {
ServiceStack.Text.JsConfig.ExcludeTypeInfo = false;
ServiceStack.Text.JsConfig.IncludeTypeInfo = true;
}
}
If you wish to use something other that the default "__type" attribute that ServiceStack uses (if you for example want to have a friendly name identifying the type rather then namespace/assembly) you can also configure your own custom type lookup as such
public class Bootstrapper {
public void Init() {
ServiceStack.Text.JsConfig.ExcludeTypeInfo = false;
ServiceStack.Text.JsConfig.IncludeTypeInfo = true;
ServiceStack.Text.JsConfig.TypeAttr = "type";
ServiceStack.Text.JsConfig.TypeFinder = type =>
{
if ("CustomTypeName".Equals(type, StringComparison.OrdinalIgnoreCase))
{
return typeof(ChildA);
}
return typeof(BaseClass);
}
}
}

C# Setting Properties using Index

I have a business class that contains many properties for various stock-exchange price types. This is a sample of the class:
public class Prices
{
public decimal Today {get; set;}
public decimal OneDay {get; set;}
public decimal SixDay {get; set;}
public decimal TenDay {get; set;}
public decimal TwelveDay {get; set;}
public decimal OneDayAdjusted {get; set;}
public decimal SixDayAdjusted {get; set;}
public decimal TenDayAdjusted {get; set;}
public decimal OneHundredDayAdjusted {get; set;}
}
I have a legacy system that supplies the prices using string ids to identify the price type.
E.g.
Today = "0D"
OneDay = "1D"
SixDay = "6D"
//..., etc.
Firstly, I load all the values to an IDictionary() collection so we have:
[KEY] VALUE
[0D] => 1.23456
[1D] => 1.23456
[6D] => 1.23456
...., etc.
Secondly, I set the properties of the Prices class using a method that takes the above collection as a parameter like so:
SetPricesValues(IDictionary<string, decimal> pricesDictionary)
{
// TODAY'S PRICE
string TODAY = "D0";
if (true == pricesDictionary.ContainsKey(TODAY))
{
this.Today = pricesDictionary[TODAY];
}
// OneDay PRICE
string ONE_DAY = "D1";
if (true == pricesDictionary.ContainsKey(ONE_DAY))
{
this.OneDay = pricesDictionary[ONE_DAY];
}
//..., ..., etc., for each other property
}
Is there a more elegant technique to set a large amount of properties?
Thanks,
j
Instead of using a string-to-decimal mapping and checking the dictionary repeatedly, use a delegate mapping/extension method:
public static class PriceConverter
{
private static readonly Dictionary<string, Action<Prices, decimal>> setters =
CreateSetterDictionary();
public static void SetPrice(this Prices p, string id, decimal newPrice)
{
Action<Prices, decimal> setter;
if (setters.TryGetValue(id, out setter))
setter(p, newPrice);
}
private static Dictionary<string, Action<Prices, decimal>>
CreateSetterDictionary()
{
var dic = new Dictionary<string, Action<Prices, decimal>>();
dic.Add("0D", (p, d) => p.Today = d);
dic.Add("1D", (p, d) => p.OneDay = d);
// etc.
return dic;
}
}
Then you can write prices.SetPrice("0D", 1.23456).
If you like, add a throw statement at the end of the SetPrice method to handle cases where the id doesn't match anything.
I would put the string variables into constants, rather than declare them every time you run the method:
private const string ONE_DAY = "D1";
If you expect the collection parameter to contain all or most of the possible values, then your code is probably cool. If you expect that the dictionary will have a small subset of the possible values, it might be more efficient to use a foreach loop and a switch statement to set values, rather then do a lookup for every possible value every time. It just depends on how many values you need to deal with and how many you get in each method call.
Define a dictionary of properties in the constructor e.g.
private Dictionary<int, PropertyInfo> propertyDictionary = new ...
MyClass()
{
this.propertyDictionary.Add(0, this.GetType().GetProperty("FirstProperty");
...
}
then access using an indexed property
decimal this[int index]
{
get
{
PropertyInfo property;
if (this.propertyDictionary.TryGetValue(index, out property))
{
// Not sure I remember the arguments right here:
property.SetValue(this, new object[] { value });
}
set
{
// Similar code
}
}
You could later on improve this code by automatically parsing the properties in the constructor using reflection,
adding all properties with an attribute that tells you what the id is.
(Instead of adding them manually in the constructor).
Just an idea:
interface IPrices_As_String{
string OD { get; set; }
// other properties here...
}
interface IPrices{
decimal Today{get; set;}
}
class Prices : IPrices, IPrices_As_String{
public decimal Today { get; set; }
public string IPrices_As_String.OD {
get { return this.Today.ToString(); }
set {
if(!String.IsNullOrEmpty(value)){
this.Today = decimal.Parse(value);
}
}
}
}
Then when I am setting the values from the legacy system, I will use the Prices class on the interface as IPrices_As_String like:
IPrices_As_String obj = new Prices();
// set values from the legacy system
IPrices obj2 = obj as IPrices; // will give me the correct object..
.
HTH.
The way I see it, you have a few options, depending on your skills, the way you are allowed to change the current POCO's or other classes:
If you must use a dictionary, create a similar dictionary which maps the "0D" etc to the OneDay names. Loop through the dictionary and assign using simple reflection.
If you can change the way the data is read, have the dictionary read with OneDay etc, instead of the "0D", which is only applicable to the external application.
Create an attribute, LegacyKeyAttribute, augment your POCO gettors/settors with this attribute. Now it becomes trivial: loop through the properties of the POCO to find the correct property for your current legacy key.
The last option requires a bit more understanding of C# than many average programmers know: writing and using attributes and reflection. However, in the end it's the cleanest and easiest solution (I'll try to come up with an example).
UPDATE: here's a little example. Meanwhile, many improvement suggestions have been posted, but none still uses attributes, while your case seems ideal. Why? It poses the least burden on existing code, I believe, and it makes reading and understanding your code even easier.
Usage:
// any price:
Prices prices = new Prices();
prices.SetPriceByLegacyName("0D", 1.2345M);
// or, your loop becomes a bit easier:
SetPricesValues(IDictionary<string, decimal> pricesDictionary)
{
foreach(string key in pricesDictionary.Keys)
{
// assuming "this" is of type Prices (you didn't specify)
this.SetPriceByLegacyName(key, pricesDictionary[key]);
}
}
The implementation:
// the simplest attribute class is enough for you:
[AttributeUsage(AttributeTargets.Property)]
public class LegacyNameAttribute : Attribute
{
public string Name { get; set; }
public LegacyNameAttribute(string name)
{
this.Name = name;
}
}
// your Prices POCO class becomes easier to read
public class Prices
{
[LegacyName("0D")] public decimal Today { get; set; }
[LegacyName("1D")] public decimal OneDay { get; set; }
[LegacyName("6D")] public decimal SixDay { get; set; }
[LegacyName("10D")] public decimal TenDay { get; set; }
[LegacyName("12D")] public decimal TwelveDay { get; set; }
[LegacyName("1DA")] public decimal OneDayAdjusted { get; set; }
[LegacyName("6DA")] public decimal SixDayAdjusted { get; set; }
[LegacyName("10DA")] public decimal TenDayAdjusted { get; set; }
[LegacyName("100DA")] public decimal OneHundredDayAdjusted { get; set; }
}
// an extension method to ease the implementation:
public static class PricesExtensions
{
public static void SetPriceByLegacyName(this Prices price, string name, decimal value)
{
if (price == null)
throw new ArgumentException("Price cannot be null");
foreach (PropertyInfo prop in price.GetType().GetProperties())
{
LegacyNameAttribute legNameAttribute = (LegacyNameAttribute)
Attribute.GetCustomAttribute(prop, typeof(LegacyNameAttribute));
// set the property if the attribute matches
if (legNameAttribute != null && legNameAttribute.Name == name)
{
prop.SetValue(price, value, null);
break; // nothing more to do
}
}
}
}
That's all there is to it. Even with all the added lines, it may well be that your total line count becomes less. But more importantly, it becomes easier to maintain and use.

Categories