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
Related
There are a lot of answers for this using a single Type with interfaces and abstract classes which all work fine for one generic T value. What I am trying to achieve I have not seen and am wondering if anyone has an idea.
Scenario
public class Field<T>
{
public Expression<Func<T,object>> FieldName { get; set; }
}
public class FValue<T, F> : Field<T>
{
public F FieldValue { get; set; }
}
//Test
var fieldList = new List<Field<Person>>();
fieldList.Add(new FValue<Person, DateTime> { FieldName=x=>x.SomeDate, FieldValue=DateTime.Now });
fieldList.Add(new FValue<Person, string> { FieldName=x=>x.SomeData, FieldValue="test" });
Ideally i want to do the following:-
The list will contain the same type for T, the type for F will change to various types like date,string etc.
When iterating over the list i want both the FieldName and FieldValue
I can't start the list using new <List<FValue<Persion,string>>();
for the obvious reason that all F values will have to be string.
Also when obtaining the FieldName somehow the value should be casted to Expression<Func<T,F>>.
Any suggestions would be appreciated.
Whenever you want to use generics you need a specific reason to use it. See this answer about when to use Generics
What is cool about generics, why use them?
As you can see in the link, one of the main reason is to have type-safe properties. This also means that your class will be limited to the specific type. And taking this in consideration, the usages of your class will be limited.
Here is how I could use your class with limited usages that don't require (boxing/unboxing), but still requires casting
private static void UseFieldList<T>(List<Field<T>> fieldList)
{
foreach (var field in fieldList)
{
var propName = field.FieldNameText;
var textField = field as FValue<T, string>;
if (textField != null)
{
// Now can use string specific functions without (boxing/unboxing)
Console.WriteLine(propName + " " + textField.FieldValue );
continue;
}
var dateField = field as FValue<T, DateTime>;
if (dateField != null)
{
// Now can use date specific functions without (boxing/unboxing)
Console.WriteLine(propName + " " + dateField.FieldValue.ToShortDateString());
continue;
}
throw new NotSupportedException("The type of the field is not supported: " + field.GetType().Name);
}
}
To get the name out of a expression you can see the answer here
Retrieving Property name from lambda expression
And to use this I would change the way you are creating the objects to something similar to the usages of Tuple:
// Newer code storing name
fieldList.Add(FValue<Person>.Create(x => x.DateOfBirth, DateTime.Now ));
fieldList.Add(FValue<Person>.Create(x => x.Name, "test"));
// Old code storing expression instead of name
fieldList.Add(new FValue<Person, DateTime> { FieldName = x => x.DateOfBirth, FieldValue = DateTime.Now });
fieldList.Add(new FValue<Person, string> { FieldName = x => x.Name, FieldValue = "test" });
// Not supported Type Int
fieldList.Add(new FValue<Person, int> {FieldName = x => x.Name, FieldValue = 12});
And here is the factory class
public class FValue<T>
{
public static Field<T> Create<F>(Expression<Func<T, F>> fieldNameExpression, F value)
{
return new FValue<T, F>
{
FieldNameText = GetPropertyInfo(fieldNameExpression).Name,
FieldValue = value
};
}
}
Results of the console:
DateOfBirth 1/19/2017
Name test
x => Convert(x.DateOfBirth) 1/19/2017
x => x.Name test
The type of the field is not supported: FValue`2
I'm not sure I see a way around this one without using Reflection. Using these classes:
public class Field<T>
{
public Expression<Func<T, object>> FieldName { get; set; }
}
public class FValue<T, F> : Field<T>
{
public F FieldValue { get; set; }
}
You can iterate over them like so:
var list = new List<Field<string>>();
list.Add(new FValue<string, int>() { FieldName = null, FieldValue = 5 });
foreach (var x in list)
{
Type[] types = x.GetType().GetGenericArguments();
// Dirty check to confirm this is an FValue not a Field
if (types.Length == 2)
{
var fieldName = x.FieldName;
object fieldValue = x.GetType().GetProperty("FieldValue").GetValue(x);
// fieldValue will be "5"
}
}
Hello I came up with situation where I am taking value from user in the form of querystring.
Name of first 2 parameter is fix,so name name of querystring is fix.After these 2 parameter,user may enter or may not enter any parameter.Parameter count can be from 1 to many and vary in datatype.
Ex: sitename/sample.aspx?username=''&userid=''&Date=
Ex: sitename/sample.aspx?username=''&userid=''&Date=&amount=
That's why I created dictionary object and storing key and value of dynamic querystring into it(not username and userid).
Dictionary<string, string> queryStringValues = new Dictionary<string, string>();
foreach (string key in Request.QueryString.AllKeys)
{
queryStringValues.Add(key, Request.QueryString[key]);
}
Here I have created dictionary of <string,string> .But my key must be string and value may not string. It can be int,date .How to take that?
At last I want to check datatype of value? How to do that?
Query string as the name says it's a string ( a collection of key value pair that consists of 2 strings ( key and value ) ) so basically you cannot know for sure if value of Date would be convertible to DateTime object.
You can though make something like factory ( which I've done few months ago ) to pair key with different Type.
To explain this in more detail :
public static class Factory
{
static Dictionary<string, Type> _fac = new Dictionary<string, Type>();
public static void Assign<T>(string key)
{
if(_fac.ContainsKey(key))
{
if(_fac[key] != typeof(T)) _fac[key] = typeof(T);
}
else
{
_fac.Add(key, typeof(T));
}
}
public static object Retrieve(string key, string value)
{
if(_fac.ContainsKey(key))
{
if(_fac[key] == typeof(string))
return value;
TypeConverter converter = TypeDescriptor.GetConverter(_fac[key]);
if(converter.CanConvertFrom(typeof(string))
return converter.ConvertFromString(value);
}
return null;
}
public static Type TypeFor(string key)
{
if(_fac.ContainsKey(key))
return _fac[key];
return null;
}
}
To use this simply do something like :
Factory.Assign<DateTime>("date");
// later on, you can retrieve value using this:
// assume query is "Date=01/01/2001"
Dictionary<string, object> queryStringValues = new Dictionary<string, object>();
foreach (string key in Request.QueryString.AllKeys)
{
queryStringValues.Add(key, Factory.Retrieve(key, Request.QueryString[key]));
}
You could use Dictionary with objects.
Dictionary<string, object> queryStringValues = new Dictionary<string, object>();
Note, after that ou should cast your objects to knwon type , something as:
queryStringValues.Add("2", true);
...
var q = queryStringValues["2"];
if (q is bool)
{
var r = !(bool)q;
}
i have to iterate a loop on about 400 different XML files and every time i will be getting different xml file.
I have about 11 nodes in the XML(all coming as String) and i am parsing this XML and storing the XML Element's values using Entity Framework in the Database (in different data types like Decimal, int, string, double)
I do not know which xml node will come as null and i do not want to add a null check for each and every node..
Is there a way to implement a common null check for the whole XML file in the loop so if any node comes as null, i can assign it to the default value of respective data type in its respective Entity.. Some thing like the code snippet shown below:-
foreach (XmlNode node in tableElements)
{
dcSearchTerm searchTermEntity = new dcSearchTerm();
//Reference keywords: creation & assignment
int IDRef = 0, salesRef = 0, visitsRef = 0, saleItemsRef = 0;
DateTime visitDateRef = new DateTime();
decimal revenueRef = 0;
int.TryParse(node["id"].InnerText, out IDRef);
searchTermEntity.SearchTerm = node["Search_x0020_Term"].InnerText;
searchTermEntity.ReferrerDomain = node["Referrer_x0020_Domain"].InnerText;
if (node["Country"] == null)
{
searchTermEntity.Country = "";
}
else
{
searchTermEntity.Country = node["Country"].InnerText;
}
DateTime.TryParse(node["Visit_x0020_Date"].InnerText, out visitDateRef);
searchTermEntity.VisitEntryPage = node["Visit_x0020_Entry_x0020_Page"].InnerText;
int.TryParse(node["Sales"].InnerText, out salesRef);
int.TryParse(node["Visits"].InnerText, out visitsRef);
decimal.TryParse(node["Revenue"].InnerText, out revenueRef);
int.TryParse(node["Sale_x0020_Items"].InnerText, out saleItemsRef);
// assigning reference values to the entity
searchTermEntity.ID = IDRef;
searchTermEntity.VisitDate = visitDateRef;
searchTermEntity.Sales = salesRef;
searchTermEntity.Visits = visitsRef;
searchTermEntity.Revenue = revenueRef;
searchTermEntity.SaleItems = saleItemsRef;
searches.Add(searchTermEntity);
return searches;
}
P.S.:- This is my first question on SO, please feel free to ask more details
Waiting for a flood of suggestions ! :)
OK, here is extension class that adds methods to Strings and XmlNodes:
public static class MyExtensions
{
// obviously these ToType methods can be implemented with generics
// to further reduce code duplication
public static int ToInt32(this string value)
{
Int32 result = 0;
if (!string.IsNullOrEmpty(value))
Int32.TryParse(value, out result);
return result;
}
public static decimal ToDecimal(this string value)
{
Decimal result = 0M;
if (!string.IsNullOrEmpty(value))
Decimal.TryParse(value, out result);
return result;
}
public static int GetInt(this XmlNode node, string key)
{
var str = node.GetString(key);
return str.ToInt32();
}
public static string GetString(this XmlNode node, string key)
{
if (node[key] == null || String.IsNullOrEmpty(node[key].InnerText))
return null;
else
return node.InnerText;
}
// implement GetDateTime/GetDecimal as practice ;)
}
Now we can rewrite your code like:
foreach (XmlNode node in tableElements)
{
// DECLARE VARIABLES WHEN YOU USE THEM
// DO NOT DECLARE THEM ALL AT THE START OF YOUR METHOD
// http://programmers.stackexchange.com/questions/56585/where-do-you-declare-variables-the-top-of-a-method-or-when-you-need-them
dcSearchTerm searchTermEntity = new dcSearchTerm()
{
ID = node.GetInt("id"),
SearchTerm = node.GetString("Search_x0020_Term"),
ReferrerDomain = node.GetString("Referrer_x0020_Domain"),
Country = node.GetString("Country"),
VisitDate = node.GetDateTime("Visit_x0020_Date"),
VisitEntryPage = node.GetString("Visit_x0020_Entry_x0020_Page"),
Sales = node.GetInt("Sales"),
Visits = node.GetInt("Visits"),
Revenue = node.GetDecimal("Revenue"),
SaleItems = node.GetDecimal("Sale_x0020_Items")
};
searches.Add(searchTermEntity);
return searches;
}
Don't forget to implement GetDateTime and GetDecimal extensions- I've left those to you ;).
You can use a monad style extension method like below. The sample provided acts only on structs. You can modify it to use for all types.
public static class NullExtensions
{
public delegate bool TryGetValue<T>(string input, out T value);
public static T DefaultIfNull<T>(this string value, TryGetValue<T> evaluator, T defaultValue) where T : struct
{
T result;
if (evaluator(value, out result))
return result;
return defaultValue;
}
public static T DefaultIfNull<T>(this string value, TryGetValue<T> evaluator) where T : struct
{
return value.DefaultIfNull(evaluator, default(T));
}
}
Example:
string s = null;
bool result = s.DefaultIfNull<bool>(bool.TryParse, true);
int r = s.DefaultIfNull<int>(int.TryParse);
NOTE: Clarified some of my question at the bottom.
I am wondering if there might be a (sane) pattern to deal with request/response from older mainframe systems? In the examples below, IQ is the request and RSIQ is the response. In the first example, I am requesting a list of all account codes and in the second request I am asking for the Closed Date for each account code. Since these are only linked by the ordinal position it is easy enough to pull the data into a structured data class. Each response in this case represents multiple records.
In the 2nd example I am requesting several bits of information for a single record. In this case, each response represents a single record and a smattering of data points.
This is the message a client sends to the server to request specific information from the database.
The inquiry message has this general format:
IQ~<msg id>~A<unit#>~B<device type>~D<acct#>~F<password>~G<file>~H<hierarchicrecordpath>~J<field>
**One field from many records**:
Beginning with first share (ordinal zero) on Account 101 return all the Share ID fields in first
message then get all Close Dates in second message. IDs and Close Dates correspond
positionally within the two responses.
IQ~1~A0~BVENDOR~D101~F7777~HSHARE=0~JID=ALL
RSIQ~1~K0~JID=0000~JID=0003~JID=0004~JID=0005~JID=0025~JID=0050
IQ~1~A0~BVENDOR~D101~F7777~HSHARE=0~JCLOSEDATE=ALL
RSIQ~1~K0~JCLOSEDATE=00000000~JCLOSEDATE=20030601~JCLOSEDATE=00000000~JCLOSEDATE=00000000~JCLOSEDATE=00000000~JCLOSEDATE=00000000
**Many fields from one record**:
Using the previous requests get additional information from open shares (two examples).
IQ~1~A0~BVENDOR~D101~F7777~HSHARE#0005~JCLOSEDATE~JSHARECODE~JDIVTYPE~JBALANCE~JAVAILABLEBALANCE
RSIQ~1~K0~JCLOSEDATE=00000000~JSHARECODE=0~JDIVTYPE=2~JBALANCE=234567~JAVAILABLEBALANCE=234567
IQ~1~A0~BVENDOR~D101~F7777~HSHARE#0025~JCLOSEDATE~JSHARECODE~JDIVTYPE~JBALANCE~JAVAILABLEBALANCE
RSIQ~1~K0~JCLOSEDATE=00000000~JSHARECODE=1~JDIVTYPE=5~JBALANCE=654321~JAVAILABLEBALANCE=654321
BACKGROUND: I am already using the Unit of Work/Repository pattern in my applications. Each application is dealing with multiple data stores (SQL DBs, Files, Web Services, Sockets, etc). The idea being that each Repository exposes a (part of the full) data model.
My initial thinking is to create the specific calls I need in the Repository, like GetAccounts(acctId) and have the method send the correct requests and then build up the object graph from all the reponses, finally returning the object graph.
I'm now looking for a design pattern to handle the internals of each of these methods without doing a ton of string.Replace() statements, or StringBuilder calls. Since the max size of any request is 8000 characters, you can see where the ~J fields can get quite complex. (And I am still looking for all the possible codes that can go in the ~J fields.)
Smallish example:
public List<SymitarAccount> GetAccounts(string accountId)
{
var retAccounts = new List<SymitarAccount>();
// Is there a pattern to do this repetitve but ever changing task? //
// Example: Mock response then handle... //
// NOTE: There will be many request/response calls here, not just one! //
var rsp = #"RSIQ~1~K0~JCLOSEDATE=00000000~JSHARECODE=1~JDIVTYPE=5~JBALANCE=654321~JAVAILABLEBALANCE=654321";
var response = rsp.Split(new[] {'~'});
foreach (var q in response)
{
if (q.StartsWith("J") && q.Contains("="))
{
// get Key Value Pair //
// map KVP to SymitarAccount data point (big ugly switch(){}??) //
sa.Id = // KVP for ID //
sa.Balanace = // KVP for BALANCE //
}
retAccounts.Add(sa);
}
return retAccounts;
}
Any thoughts or ideas?
NOTE: I am using C# (latest).
ADDITION #1:
public List<SymitarAccount> GetAccounts(string accountId)
{
var retAccounts = new List<SymitarAccount>();
// Get all account IDs...
var response = UnitOfWork.SendMessage("IQ~1~A0~BVENDOR~D101~F7777~HSHARE=0~JID=ALL");
ParseResponse(response, ref retAccounts);
// Get all account close dates (00000000 means it is open)...
response = UnitOfWork.SendMessage("IQ~1~A0~BVENDOR~D101~F7777~HSHARE=0~JCLOSEDATE=ALL");
ParseResponse(response, ref retAccounts);
// Get extra info for all OPEN accounts...
foreach (var account in retAccounts.Where(a => !a.IsClosed))
{
var request = "IQ~1~A0~BVENDOR~D101~F7777~HSHARE#[acct]~JCLOSEDATE~JSHARECODE~JDIVTYPE~JBALANCE~JAVAILABLEBALANCE";
request = request.Replace("[acct]", account.Id.ToString("0000"));
response = UnitOfWork.SendMessage(request);
ParseResponse(response, ref retAccounts, account.Id);
}
return retAccounts;
}
private void ParseResponse(string response, ref List<SymitarAccount> accountList, int? id = null)
{
var list = response.Split(new[] {'~'});
var index = 0;
var chain = new ChainInquiryAccountInfo();
var parser = chain.Parser;
foreach (var q in list.Where(q => q.StartsWith("J"))) // && q.Contains("=")))
{
if (accountList.Count < index || accountList[index] == null)
accountList.Add(new SymitarAccount {PositionalIndex = index});
var val = q.Split(new[] {'='});
if ((id.HasValue && accountList[index].Id == id.Value) || !id.HasValue)
accountList[index] = parser.Parse(val, accountList[index]);
index++;
}
}
You example is in fact deserialization, not from XML or JSON but from some custom text format. You can go with the direction of other serializers then, when you create classes and attribute their fields to help serializing/deserializing. This can be called Attributed Serializer Pattern I believe...
Let's create some custom attribute to annotate serialized classes:
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
sealed class SomeDataFormatAttribute : Attribute
{
readonly string name;
// This is a positional argument
public SomeDataFormatAttribute(string positionalString)
{
this.name = positionalString;
}
public string Name
{
get { return name; }
}
}
and then you can describe your data objects as:
class SymitarAccount
{
[SomeDataFormat("CLOSEDATE")]
public string CloseDate;
[SomeDataFormat("SHARECODE")]
public int ShareCode;
}
Now you need serializer/deserializer based on Reflection, that will match attributed fields with string. Here I use regular expressions (and no error checking for simplicity):
public class SomeDataFormatDeserializer
{
public static T Deserlize<T>(string str) where T : new()
{
var result = new T();
var pattern = #"RSIQ~1~K0(?:~J(\w+=\d+))*";
var match = Regex.Match(str, pattern);
// Get fields of type T
var fields = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (var field in fields)
{
// Get out custom attribute of this field (might return null)
var attr = field.GetCustomAttribute(typeof(SomeDataFormatAttribute)) as SomeDataFormatAttribute;
// Find regex capture that starts with attributed name (might return null)
var capture = match.Groups[1].Captures
.Cast<Capture>()
.FirstOrDefault(c => c.Value.StartsWith(attr.Name));
if (capture != null)
{
var stringValue = capture.Value.Split('=').Last();
// Convert string to the proper type (like int)
var value = Convert.ChangeType(stringValue, field.FieldType);
field.SetValue(result, value);
}
}
return result;
}
}
And then you can use it as simple as:
public static List<SymitarAccount> GetAccounts(string accountId)
{
var retAccounts = new List<SymitarAccount>();
var responses = new List<string>() { #"RSIQ~1~K0~JCLOSEDATE=00000000~JSHARECODE=1" };
foreach (var response in responses)
{
var account = SomeDataFormatDeserializer.Deserlize<SymitarAccount>(response);
retAccounts.Add(account);
}
return retAccounts;
}
Note: SomeDataFormatDeserializer is written for clarity, not performance. For sure it can be optimized (like caching GetFields etc.)
MY SOLUTION:
Attribute definition:
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
internal sealed class SymitarInquiryDataFormatAttribute : Attribute
{
private readonly string _name;
// This is a positional argument
public SymitarInquiryDataFormatAttribute(string positionalString) { this._name = positionalString; }
public string Name { get { return _name; } }
}
Data class:
[Serializable]
public class SymitarAccount
{
public int PositionalIndex;
public bool IsClosed{get { return CloseDate.HasValue; }}
[SymitarInquiryDataFormatAttribute("ID")]
public int Id;
[SymitarInquiryDataFormatAttribute("CLOSEDATE")]
public DateTime? CloseDate;
[SymitarInquiryDataFormatAttribute("DIVTYPE")]
public int DivType;
[SymitarInquiryDataFormatAttribute("BALANCE")]
public decimal Balance;
[SymitarInquiryDataFormatAttribute("AVAILABLEBALANCE")]
public decimal AvailableBalance;
}
Extensions:
public static class ExtensionSymitar
{
public static List<string> ValueList(this string source, string fieldType)
{
var list = source.Split('~').ToList();
return list.Where(a => a.StartsWith(fieldType)).ToList();
}
public static string KeyValuePairs(this string source, string fieldType)
{
return source.ValueList(fieldType).Aggregate(string.Empty, (current, j) => string.Format("{0}~{1}", current, j));
}
public static bool IsMultiRecord(this string source, string fieldType)
{
return source.ValueList(fieldType)
.Select(q => new Regex(Regex.Escape(q.Split('=').First())).Matches(source).Count > 1).First();
}
public static int ParseInt(this string val, string keyName)
{
int newValue;
if (!int.TryParse(val, out newValue))
throw new Exception("Could not parse " + keyName + " as an integer!");
return newValue;
}
public static decimal ParseMoney(this string val, string keyName)
{
decimal newValue;
if (!decimal.TryParse(val, out newValue))
throw new Exception("Could not parse " + keyName + " as a money amount!");
return newValue;
}
public static DateTime? ParseDate(this string val, string keyName)
{
if (val.Equals("00000000")) return null;
var year = val.Substring(0, 4).ToInt();
var mon = val.Substring(4, 2).ToInt();
var day = val.Substring(6, 2).ToInt();
if (year <= 1800 || year >= 2200 || mon < 1 || mon > 12 || day < 1 || day > 31)
throw new Exception("Could not parse " + keyName + " as a date!");
return new DateTime(year, mon, day);
}
}
Deserializer:
public class SymitarInquiryDeserializer
{
/// <summary>
/// Deserializes a string of J field key value pairs
/// </summary>
/// <param name="str">The request or response string</param>
/// <param name="source">Optional: Use this if you are adding data to the source object</param>
/// <param name="fieldName">Optional: Use this if you are only populating a single property and know what it is</param>
/// <typeparam name="T">The target class type to populate</typeparam>
/// <returns>New T Object or optional Source Object</returns>
public static T DeserializeFieldJ<T>(string str, T source = null, string fieldName = null) where T : class, new()
{
var result = source ?? new T();
const string pattern = #"(?:~J(\w+=\d+))*";
var match = Regex.Match(str, pattern);
// Get fields of type T
var fields = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance).ToList();
if (fieldName != null && fieldName.StartsWith("J")) fieldName = fieldName.Replace("J", "");
if (!fieldName.IsNullOrEmpty())
{
var field = fields.FirstOrDefault(a => a.Name.Equals(fieldName, StringComparison.CurrentCultureIgnoreCase));
var stringValue = GetValue(field, match);
if (!stringValue.IsNullOrEmpty())
SetProperty(field, stringValue, result);
}
else
{
foreach (var field in fields)
{
var stringValue = GetValue(field, match);
if(!stringValue.IsNullOrEmpty())
SetProperty(field, stringValue, result);
}
}
return result;
}
private static string GetValue(FieldInfo field, Match match)
{
// Get out custom attribute of this field (might return null)
var attr = field.GetCustomAttribute(typeof(SymitarInquiryDataFormatAttribute)) as SymitarInquiryDataFormatAttribute;
if (attr == null) return null;
// Find regex capture that starts with attributed name (might return null)
var capture = match.Groups[1]
.Captures
.Cast<Capture>()
.FirstOrDefault(c => c.Value.StartsWith(attr.Name, StringComparison.CurrentCultureIgnoreCase));
return capture == null ? null : capture.Value.Split('=').Last();
}
private static void SetProperty<T>(FieldInfo field, string stringValue, T result)
{
// Convert string to the proper type (like int)
if (field.FieldType.FullName.Contains("Int32"))
field.SetValue(result, stringValue.ParseInt(field.Name));
else if (field.FieldType.FullName.Contains("Decimal"))
field.SetValue(result, stringValue.ParseMoney(field.Name));
else if (field.FieldType.FullName.Contains("DateTime"))
field.SetValue(result, stringValue.ParseDate(field.Name));
else
{
var value = Convert.ChangeType(stringValue, field.FieldType);
field.SetValue(result, value);
}
}
}
Finally, in my repository:
public List<SymitarAccount> GetAccounts(string accountId)
{
var accountList = new List<SymitarAccount>();
// build request, get response, parse it...
var request = "IQ~1~A20424~BAUTOPAY~D101~F7777~HSHARE=0~JID=ALL";
var response = UnitOfWork.SendMessage(request);
ParseResponse(response, ref accountList);
foreach (var account in accountList.Where(a => a.IsClosed == false))
{
request = "IQ~1~A20424~BAUTOPAY~D101~F7777~HSHARE#" + account.Id.ToString("0000") + "~JCLOSEDATE~JSHARECODE~JDIVTYPE~JBALANCE~JAVAILABLEBALANCE";
response = UnitOfWork.SendMessage(request);
ParseResponse(response, ref accountList, account.Id);
}
return accountList;
}
private void ParseResponse(string response, ref List<SymitarAccount> accountList, int? id = null)
{
var index = 0;
var list = response.ValueList(fieldType: "J");
var jString = response.KeyValuePairs(fieldType: "J");
var isMultiRecord = response.IsMultiRecord(fieldType: "J");
SymitarAccount account;
if (isMultiRecord && !id.HasValue)
foreach (var q in list.Where(a => a.StartsWith("J")))
{
// Add object if we don't yet have it in the collection...
if (accountList.Count <= index)
accountList.Add(new SymitarAccount { PositionalIndex = index });
account = accountList.FirstOrDefault(a => a.PositionalIndex == index);
SymitarInquiryDeserializer.DeserializeFieldJ("~" + q, account, q.Split('=').First());
index++;
}
else if(id.HasValue)
{
account = accountList.FirstOrDefault(a => a.Id == id.Value);
SymitarInquiryDeserializer.DeserializeFieldJ(jString, account);
}
}
The difference between the 2 calls to ParseResponse is, in the first case, I am asking for multiple records to be returned (only 1 data property though!) while in the second case I am requesting extra data properties for a single record be sent back.
I was wondering if there is something in c# to be able to pass a member of a class to another function that will use this member to get a value. So get a value of a field determined only which one at runtime. Something like in other languages (PHP at least I think) that you can do
a.b = "something"
but also
a["b"] = "something";
edit: actually not so good an example since a string is used, sorry
For clarity an example of what I'd like to be able to do:
class A
{
int x;
int y;
}
void somethingsomething<T>(T class, SomeMagicFieldClass f)
{
dosomethingwith(somemethodthatgivesmethevalueoffield(class, f));
}
Where then I can call the method like this:
A a = new A();
somethingsomething(a, A.x); //hypothetical notation
somethingsomething(a, A.y);
I now have something similar where I do:
somethingsomething(a, "x");
somethingsomething(a, "y");
I then go find the field using introspection API (also trying GetProperty)
MemberInfo memberInfo = item.GetType().GetField(fieldName);
This works but the disadvantage is that the fields passed as a string won't get updated when "refactoring" fieldnames in visual studio, so I was thinking maybe there exists something like this in c# that would get refactored automatically when changing field names?
Thanks a lot for reading this boring question
Your example looks a lot like a LINQ key selector, in that form it would look like:
A a = new A();
somethingsomething(a, p => p.x);
You can do some nice refactor-friendly things with LINQ Expressions. Here is a snippet of utilty code I used for such occasions. It allows you to get the Name, Type and Value of a property (it won't work with fields without modifications). There's also a setter for the value.
public static void Main(string[] args) {
var test = new { Test1 = 42, Test2 = "123", Test3 = 3.14195 };
somethingSomething(test, t => t.Test1);
somethingSomething(test, t => t.Test2);
somethingSomething(test, t => t.Test3);
}
static void somethingSomething<TObj,TProperty>(TObj obj, Expression<Func<TObj,TProperty>> expr) {
var accessor = GetMemberAccessor(expr, obj);
String name = accessor.Name;
TProperty value = accessor.Value;
String typeName = accessor.Type.Name;
Console.WriteLine("{0} = {1} ({2})", name, value, typeName);
}
The output of that would be:
Test1 = 42 (Int32)
Test2 = 123 (String)
Test3 = 3.14195 (Double)
To make this work, I used the following helper function and class:
public static MemberAccessor<TReturn> GetMemberAccessor<TObj,TReturn>(Expression<Func<TObj, TReturn>> expr, TObj tar) {
var body = expr.Body;
MemberExpression memberExpression = null;
if (body is UnaryExpression) {
var ue = (UnaryExpression)body;
memberExpression = (MemberExpression)ue.Operand;
} else if (body is MemberExpression)
memberExpression = (MemberExpression)body;
else
throw new NotImplementedException("can't get MemberExpression");
String name = memberExpression.Member.Name;
return new MemberAccessor<TReturn>(tar, name);
}
public class MemberAccessor<T> {
private readonly PropertyDescriptor propertyDesc;
private readonly Object target;
public MemberAccessor(Object target, String propertyName) {
this.target = target;
this.propertyDesc = TypeDescriptor.GetProperties(target)[propertyName];
}
public String Name {
get { return propertyDesc.Name; }
}
public Type Type {
get { return propertyDesc.PropertyType; }
}
public T Value {
get { return (T)Convert.ChangeType(propertyDesc.GetValue(target), typeof(T)); }
set { propertyDesc.SetValue(target, value); }
}
}
Mr. Plunkett is correct; a dynamic type will do the job. Luckily, the .NET 4 team included a handy object called the ExpandoObject that solves that for you.
You asked how to
pass a member of a class to another
function that will use this member to
get a value
You can usedelegates for this
class A
{
public string aField;
public string aProperty{get{return "someval";}}
public string aMemberFunction(){return "someval";}
}
void get_a_value(Func<string> func)
{
string theValue = func();
}
// use it:
A a = new A();
get_a_value( () => a.aField);
get_a_value( () => a.aProperty);
get_a_value( () => a.aMemberFunction());
What you don't get this way, of course, is a separation of parameters for the memberfunction and the object you are passing.