How do I find an object(s) by its hashcode? - c#

Is there an easy way to dig in to an object and find a property or field by its hashcode? This could be a nested property or a value in a collection. The reason I ask is that I occasionally get WPF warnings that look like:
System.Windows.ResourceDictionary Warning: 9 : Resource not found;
ResourceKey='#FF000000'; ResourceKey.HashCode='51639504';
ResourceKey.Type='System.Windows.Media.SolidColorBrush'
The warning doesn't always appear and I'm having the hardest time tracking it down. I figure if I knew which object had that hashcode, I could get closer to fixing this. For instance, if I had this object:
var first = new { a = 1, b = 2, d = new { aa = 11, bb = 22 } };
and called this on it:
string str = FindHashCode(first, 22);
the result would be:
"Anon > d > bb.hashcode = 22"
or something similar. (I'm ignoring hashcode collisions for now)
Edit: Here's what I'll use based on #Alberto's answer. It searches both fields and properties whether public or non-public. It includes support for IEnumerables (Lists, Arrays, etc.) and more specifically IDictionaries. It also handles hashcode collisions. If two objects have the same hashcode, the StringBuilder will have a separate line for each object.
using System.Reflection;
static string FindHashCode(object o, int hashCode)
{
StringBuilder strb = new StringBuilder();
FindHashCode(o, hashCode, o.GetType().Name, strb);
return strb.ToString().Trim();
}
static void FindHashCode(object o, int hashCode, string path, StringBuilder strb)
{
if (o.GetHashCode() == hashCode)
{
strb.AppendLine(path + ".hashcode = " + hashCode);
}
foreach (var field in GetFieldInfo(o))
{
if (field.Item1 == null || object.ReferenceEquals(o, field.Item1))
continue;
Type type = field.Item1.GetType();
if (type.IsPrimitive)
{
if(field.Item1.GetHashCode() == hashCode)
strb.AppendLine(path + " > " + field.Item2 + ".hashcode = " + hashCode);
}
else
{
FindHashCode(field.Item1, hashCode, path + " > " + field.Item2, strb);
}
}
}
static IEnumerable<Tuple<object, string>> GetFieldInfo(object arg)
{
var ienum = arg as System.Collections.IEnumerable;
var idict = arg as System.Collections.IDictionary;
if (ienum == null && idict == null)
{
BindingFlags bf = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
Type type = arg.GetType();
var list = type.GetFields(bf).Select(s => new Tuple<object, string>(s.GetValue(arg), s.Name)).Concat(
type.GetProperties(bf).Select(s => new Tuple<object, string>(s.GetValue(arg, null), s.Name)));
foreach (var item in list)
{
yield return item;
}
}
else if (idict != null)
{
foreach (System.Collections.DictionaryEntry item in idict)
{
yield return new Tuple<object, string>(item.Key, string.Format("Dict[{0}].Key", item.Key));
yield return new Tuple<object, string>(item.Value, string.Format("Dict[{0}].Value", item.Key));
}
}
//note that dictionaries implement IEnumerable
else if (ienum != null && !(ienum is string))
{
int count = 0;
foreach (var item in ienum)
{
yield return new Tuple<object, string>(item, string.Format("this[{0}]", count));
count++;
}
}
}

Here an implementation that search recursively in an object graph for a property with a specific hashcode:
static string FindHashCode(object o, int hashCode)
{
return FindHashCodeImpl(o,hashCode, o.GetType().Name);
}
static string FindHashCodeImpl(object o, int hashCode, string partialPath)
{
var type = o.GetType();
var properties = type.GetProperties();
foreach (var property in properties)
{
var propValue = property.GetValue(o);
if (propValue.GetHashCode() == hashCode)
{
return partialPath + " > " + property.Name + ".hashcode = " + hashCode;
}
var path = FindHashCodeImpl(propValue, hashCode, partialPath + " > " + property.Name);
if (path != null)
{
return path;
}
}
return null;
}
Use it like:
var o = new { a = 1, b = 2, d = new { aa = 11, bb = 22 } };
var s = FindHashCode(o, 22); //Output "<>f__AnonymousType1`3 > d > bb.hashcode = 22"
You should extend it to search inside fields as well.
P.S I didn't test it for every scenario, but it should work...

There's no automatic way to search a graph of objects for a member whose hashcode matches a certain value. But if you know the structure (like a List) then you can go along and GetHashCode() for each one and return the matches (of which there could be many). I think you must have some concept of the data you're working with, some container such as a list or tree, right?
The Visual Studio debugger also lets you assign Object IDs to items in the watches so that you can know that two references are to the same item, in case that helps.
I think there are faster ways to find your bug.
Trace WPF System.Windows.ResourceDictionary warning Resource not found

Related

Compare two lists on one property and dont add duplicate

Hello so for some reason using various examples i havent been able to solve this.
So i have two lists one containing global values and one vlues that are set on a specific property. What i want to achieve is compare the two lists and keep the specific ones and then add the global ones that are not in the specific list based on its name.
i have tried this
var pidConfigValues = await _database.GetConfigurationValuesForPid(productGroup);
var globalConfigValues = await _database.GetGlobalConfigurationValues();
var allConfigs = pidConfigValues.Where(c => globalConfigValues.All(d => c.Name != d.Name)).ToList();
I guess something is wrong with the Where condition because the allConfigs ends up as empty. The both variables that gets compared are lists of same type of object
Example data
pidConfigValues would consist of objects like
Name: config.myConfig,
Pid: 2,
Value: 1
and globalConfigValues would be like
Name: config.myConfig,
Pid: Null,
Value: 0
Name: config.someOtherConfig,
Pid: Null,
Value: 1
So in the example above i would want allConfigs to be
Name: config.myConfig,
Pid: 2,
Value: 1
Name: config.someOtherConfig,
Pid: Null,
Value: 1
So in allConfigs only the config.myConfig with pid would be shown and from global only add the ones that does not exist in the specific one
Here is one way of doing it:
var pidConfigValues = new List<Config>()
{
new Config() { Name = "config.myConfig", Pid = 2, Value = 1}
};
var globalConfigValues = new List<Config>()
{
new Config() { Name = "config.myConfig", Pid = null, Value = 0},
new Config() { Name = "config.someOtherConfig", Pid = null, Value = 1}
};
var result = pidConfigValues.Concat(globalConfigValues)
.GroupBy(x => x.Name)
.Select(x => x.First()) //if multiple entities have the same name pick the first one which will be the one from pidConfigValues
One solution would be to use Union in combination with a custom EqualityComparer that compares the configs based on their Name-property:
// in your code:
var allConfigs = pidConfigValues.Union(globalConfigValues, new MyConfigComparer()).ToList();
// sample for the comparer:
public class MyConfigComparer : IEqualityComparer<MyConfig>
{
public bool Equals(MyConfig c1, MyConfig c2)
{
if (object.ReferenceEquals(c1, c2))
return true;
if (c1 == null || c2 == null)
return false;
return c1.Name.Equals(c2.Name, StringComparison.Ordinal);
}
public int GetHashCode(MyConfig x)
{
return x.Name.GetHashCode();
}
}
Ciao, you can use Distinct (by rewriting EqualityComparer). Here working example:
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var pidConfigValues = new List<Configuration>();
var globalConfigValues = new List<Configuration>();
Configuration pidConfigValue = new Configuration("config.myConfig", 2, 1);
Configuration globalConfigValue1 = new Configuration("config.myConfig", null, 0);
Configuration globalConfigValue2 = new Configuration("config.someOtherConfig", null, 1);
globalConfigValues.Add(globalConfigValue1);
pidConfigValues.Add(pidConfigValue);
globalConfigValues.Add(globalConfigValue2);
List<Configuration> result = pidConfigValues.Concat(globalConfigValues)
.Distinct(new ConfigurationEqualityComparer()).ToList();
Console.WriteLine(String.Join(",", result));
Console.ReadLine();
}
}
public class Configuration
{
public string _name = "";
public Nullable<int> _pid = null;
public int _value = -1;
public Configuration(string name, Nullable<int> pid, int value)
{
this._name = name;
this._pid = pid;
this._value = value;
}
public override string ToString()
{
return "Name: " + this._name + " PID:" + this._pid + " Value:" + this._value + Environment.NewLine;
}
}
public class ConfigurationEqualityComparer
: EqualityComparer<Configuration>
{
public override bool Equals(Configuration c1, Configuration c2)
{
if (c1 == null && c2 == null)
return true;
else if (c1 == null || c2 == null)
return false;
else if (c1._name.Equals(c2._name))
{
if (c1._pid == null || c2._pid == null) return true;
else return false;
}
else
return false;
}
public override int GetHashCode(Configuration cnf)
{
int hCode = cnf._value ^ cnf._value;
return hCode.GetHashCode();
}
}
}
Explanation: Concat two lists and get only Distinct values. Equality comparer must be rewrited because we are using objects so we have to define which object is equal to another.
So your case is complicated to be solved by simple union or join operation. But simple enough to be solved by some simple select and concat operations.
What you need is to loop all loaded pidConfigValues and override a specific property with the global configuration, and then create a collection containing all unique configurations. Is that correct?
If so the solution could be like this:
var pidConfigValues = await _database.GetConfigurationValuesForPid(productGroup);
var globalConfigValues = await _database.GetGlobalConfigurationValues();
// loop through all pidConfigs and override their Pid value if matching global config exists
var allConfigs = pidConfigValues.Select(c =>
{
var matchingGlobalConfig = globalConfigValues.FirstOrDefault(g => g.Name == c.Name);
if (matchingGlobalConfig != null)
{
c.Pid = matchingGlobalConfig.Pid;
}
return c;
}).ToList();
// Find all global configs that are not matching any pidConfigValues
var productNames = pidConfigValues.Select(p => p.Name).ToArray();
var nonMatchingGlobalConfigs = globalConfigValues.Where(g => !productNames.Contains(g.Name)).ToArray();
// add non-matching global-configs to all-configs collection
allConfigs = allConfigs.Concat(nonMatchingGlobalConfigs).ToArray();

Getting expression text

I want to pass the name of a property of a model to a method. Instead of using the name as string, I am using lambda expression as it is easy to make a typo, and also property names may be changed. Now if the property is a simple property (e.g: model.Name) I can get the name from the expression. But if it is a nested property (e.g: model.AnotherModel.Name) then how can I get full text ("AnotherModel.Name") from the expression. For example, I have the following classes:
public class BaseModel
{
public ChildModel Child { get; set; }
public List<ChildModel> ChildList { get; set; }
public BaseModel()
{
Child = new ChildModel();
ChildList = new List<ChildModel>();
}
}
public class ChildModel
{
public string Name { get;set; }
}
public void GetExpressionText<T>(Expression<Func<T, object>> expression)
{
string expText;
//what to do??
return expText;
}
GetExpressionText<BaseModel>(b => b.Child); //should return "Child"
GetExpressionText<BaseModel>(b => b.Child.Name); //should return "Child.Name"
GetExpressionText<BaseModel>(b => b.ChildList[0].Name); //should return "ChildList[0].Name"
My first thought was to use expression.Body.ToString() and tweak that a bit, but you would still need to deal with Unary (convert) etc. Assuming this is for logging and you want more control, the below can be used for formatting as wanted (e.g. if you want Child->Name for display purposes, string.Join("->",..) can be used). It may not be complete, but should you find any unsupported types, they should be easy to add.
PS: this post was generated before the question was closed. Just noticed it was reopend and submitting it now, but I haven't checked if particulars have been changed.
public string GetName(Expression e, out Expression parent)
{
if(e is MemberExpression m){ //property or field
parent = m.Expression;
return m.Member.Name;
}
else if(e is MethodCallExpression mc){
string args = string.Join(",", mc.Arguments.SelectMany(GetExpressionParts));
if(mc.Method.IsSpecialName){ //for indexers, not sure this is a safe check...
return $"{GetName(mc.Object, out parent)}[{args}]";
}
else{ //other method calls
parent = mc.Object;
return $"{mc.Method.Name}({args})";
}
}
else if(e is ConstantExpression c){ //constant value
parent = null;
return c.Value?.ToString() ?? "null";
}
else if(e is UnaryExpression u){ //convert
parent= u.Operand;
return null;
}
else{
parent =null;
return e.ToString();
}
}
public IEnumerable<string> GetExpressionParts(Expression e){
var list = new List<string>();
while(e!=null && !(e is ParameterExpression)){
var name = GetName(e,out e);
if(name!=null)list.Add(name);
}
list.Reverse();
return list;
}
public string GetExpressionText<T>(Expression<Func<T, object>> expression) => string.Join(".", GetExpressionParts(expression.Body));
You could use the C# 6.0 feature: nameof(b.Child) "Used to obtain the simple (unqualified) string name of a variable, type, or member."
which will also change on renaming. But this will only return the propertyname and not the complete path. Returning a complete path will be difficult, because only one instance is passed.
Closest i know right now is by simply using expression.Body.ToString() which would result in b.ChildList.get_Item(0).Name as a result.
You would still have to remove the first b. from the string if not wanted, and you could go even further to your intended output with Regex by replacing the get_Item(0) with the typical Index-Accessor.
(Also i had to make the ChildList and the Name-Property of ChildModel public to get it to work)
This Should get you most of the way there:
public static string GetFullPath<T>(Expression<Func<T>> action)
{
var removeBodyPath = new Regex(#"value\((.*)\).");
var result = action.Body.ToString();
var replaced = removeBodyPath.Replace(result, String.Empty);
var seperatedFiltered = replaced.Split('.').Skip(1).ToArray();
return string.Join(".", seperatedFiltered);
}
It gets ugly quite quickly...
public static string GetExpressionText<T>(Expression<Func<T, object>> expression)
{
bool needDot = false;
Expression exp = expression.Body;
string descr = string.Empty;
while (exp != null)
{
if (exp.NodeType == ExpressionType.MemberAccess)
{
// Property or field
var ma = (MemberExpression)exp;
descr = ma.Member.Name + (needDot ? "." : string.Empty) + descr;
exp = ma.Expression;
needDot = true;
}
else if (exp.NodeType == ExpressionType.ArrayIndex)
{
// Array indexer
var be = (BinaryExpression)exp;
descr = GetParameters(new ReadOnlyCollection<Expression>(new[] { be.Right })) + (needDot ? "." : string.Empty) + descr;
exp = be.Left;
needDot = false;
}
else if (exp.NodeType == ExpressionType.Index)
{
// Object indexer (not used by C#. See ExpressionType.Call)
var ie = (IndexExpression)exp;
descr = GetParameters(ie.Arguments) + (needDot ? "." : string.Empty) + descr;
exp = ie.Object;
needDot = false;
}
else if (exp.NodeType == ExpressionType.Parameter)
{
break;
}
else if (exp.NodeType == ExpressionType.Call)
{
var ca = (MethodCallExpression)exp;
if (ca.Method.IsSpecialName)
{
// Object indexer
bool isIndexer = ca.Method.DeclaringType.GetDefaultMembers().OfType<PropertyInfo>().Where(x => x.GetGetMethod() == ca.Method).Any();
if (!isIndexer)
{
throw new Exception();
}
}
else if (ca.Object.Type.IsArray && ca.Method.Name == "Get")
{
// Multidimensiona array indexer
}
else
{
throw new Exception();
}
descr = GetParameters(ca.Arguments) + (needDot ? "." : string.Empty) + descr;
exp = ca.Object;
needDot = false;
}
}
return descr;
}
private static string GetParameters(ReadOnlyCollection<Expression> exps)
{
var values = new string[exps.Count];
for (int i = 0; i < exps.Count; i++)
{
if (exps[i].NodeType != ExpressionType.Constant)
{
throw new Exception();
}
var ce = (ConstantExpression)exps[i];
// Quite wrong here... We should escape string values (\n written as \n and so on)
values[i] = ce.Value == null ? "null" :
ce.Type == typeof(string) ? "\"" + ce.Value + "\"" :
ce.Type == typeof(char) ? "'" + ce.Value + "\'" :
ce.Value.ToString();
}
return "[" + string.Join(", ", values) + "]";
}
The code is quite easy to read, but it is quite long... There are 4 main cases: MemberAccess, that is accessing a property/field, ArrayIndex that is using the indexer of a single-dimensional array, Index that is unused by the C# compiler, but that should be using the indexer of an object (like the [...] of the List<> you are using), and Call that is used by C# for using an indexer or for accessing multi-dimensional arrays (new int[5, 4]) (and for other method calls, but we disregard them).
I support multidimensional arrays, jagged array s(arrays of arrays, new int[5][]) or arrays of indexable objects (new List<int>[5]) or indexable objects of indexable objects (new List<List<int>>). There is even support for multi-property indexers (indexers that use more than one key value, like obj[1, 2]). Small problem: printing the "value" of the indexers: I support only null, integers of various types, chars and strings (but I don't escape them... ugly... if there is a \n then it won't be printed as \n). Other types are not really supported... They will print what they will print (see GetParameters() if you want)

Compare Two Liste <T>

how can i compare 2 list ?
public class Pers_Ordre : IEqualityComparer<Pers_Ordre>
{
int _ordreId;
public int LettreVoidID
{
get { return _LettreVoidID; }
set { _LettreVoidID = value; }
}
string _OrdreCummul;
public string OrdreCummul
{
get { return _OrdreCummul; }
set { _OrdreCummul = value; }
}
// Products are equal if their names and product numbers are equal.
public bool Equals(Pers_Ordre x, Pers_Ordre y)
{
//Check whether the compared objects reference the same data.
if (Object.ReferenceEquals(x, y)) return true;
//Check whether any of the compared objects is null.
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
//Check whether the products' properties are equal.
return x.LettreVoidID == y.LettreVoidID && x.OrdreCummul == y.OrdreCummul;
}
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
public int GetHashCode(Pers_Ordre product)
{
//Check whether the object is null
if (Object.ReferenceEquals(product, null)) return 0;
//Get hash code for the Name field if it is not null.
int hashProductName = product.OrdreCummul == null ? 0 : product.OrdreCummul.GetHashCode();
//Get hash code for the Code field.
int hashProductCode = product.LettreVoidID.GetHashCode();
//Calculate the hash code for the product.
return hashProductName ^ hashProductCode;
}
}
and i compare like this:
private void simpleButton_Comparer_Click(object sender, EventArgs e)
{
string LeFile_Client = System.IO.Path.Combine(appDir, #"FA.csv");
string LeFile_Server = System.IO.Path.Combine(appDir, #"FA_Server.csv");
List<Pers_Ordre> oListClient = Outils.GetCsv(LeFile_Client).OrderBy(t => t.LettreVoidID).ToList();
List<Pers_Ordre> oListServert = Outils.GetCsvServer(LeFile_Server).OrderBy(t => t.LettreVoidID).ToList();
List<Pers_Ordre> LeDiff = new List<Pers_Ordre>();
LeDiff = oListServert.Except(oListClient).ToList();
string Noid = "", OdreID = "";
foreach (var oDiff in LeDiff)
{
Noid += oDiff.LettreVoidID + " ";
OdreID += oDiff.OrdreCummul + " ";
}
MessageBox.Show(Noid + "--" + OdreID);
}
i can not get the right result.
The Lists contain class objects and we would like to iterate through one list, looking for the same item in a second List and report any differences.
to get object that contains in List A but not in List B
and vice versa.
Your current .Except() call will find items from Server that are missing on the client, but it will not find items on the client that are missing on the server.
Try this:
private void simpleButton_Comparer_Click(object sender, EventArgs e)
{
string LeFile_Client = System.IO.Path.Combine(appDir, #"FA.csv");
string LeFile_Server = System.IO.Path.Combine(appDir, #"FA_Server.csv");
var ListClient = Outils.GetCsv(LeFile_Client).OrderBy(t => t.LettreVoidID);
var ListServer = Outils.GetCsvServer(LeFile_Server).OrderBy(t => t.LettreVoidID);
var LeDiff = ListServer.Except(ListClient).Concat(ListClient.Except(ListServer));
var result = new StringBuilder();
foreach (var Diff in LeDiff)
{
result.AppendFormat("{0} --{1} ", Diff.LettreVoidID, Diff.OrdreCummul);
}
MessageBox.Show(Noid.ToString() + "--" + OdreID);
}
This code should also be significantly faster than your original, as it avoids loading the results into memory until it builds the final string. This code in performs the equivalent of two separate sql LEFT JOINs. We could make it faster still by doing one FULL JOIN, but that would require writing our own linq operator method as well.

call a generic method with an object of an unknown type

I have this method
public string DictionaryToString<T, U>(Dictionary<T, U> dict)
{
var valueStrings = dict.Select(x => x.Key.ToString() + ": " + x.Value.ToString());
return String.Join("\n", valueStrings);
}
And I have this object that I want to pass into it
if ((value !=null) && value.GetType().IsGenericType &&
value.GetType().GetGenericTypeDefinition() == typeof (Dictionary<,>))
{
var castValue = value as Dictionary<,>; // this cast does not work
return DictionaryToString(castValue);
}
else
{
return value.ToString();
}
I can use reflection code like this in .Net 4.5
var targetMethodInfo = typeof(MyType).GetMethod("DictionaryToString");
var valueTypeArgs = value.GetType().GenericTypeArguments;
var genericMethod = targetMethodInfo.MakeGenericMethod(valueTypeArgs);
var result = genericMethod.Invoke(this, new[] {value });
return result.ToString();
ButType.GenericTypeArguments is new in .Net 4.5. So how can I do that cast in .Net 4.0?
You are only calling ToString on the key and value, so simply have this method take an IDictionary (non-generic), you are not using anything in there that is type-specific to T or U.
You can then simply cast all arguments to IDictionary:
var d = arg as IDictionary;
if (d != null)
{
var res = DictionaryToString(d);
}
You may also need to amend the DictionaryToString implementation:
static string DictionaryToString(IDictionary d)
{
var vals = new List<string>();
foreach (DictionaryEntry de in d)
{
vals.Add(de.Key.ToString() + ": " + de.Value.ToString());
}
return String.Join("\n", vals);
}
Alternatively, if you really want to use LINQ, you could try casting to dynamic (it isn't possible to cast to anything else as this could be a generic dictionary (KeyValuePair<>) or non-generic hashtable (DictionaryEntry)):
var valueStrings = d.Cast<dynamic>().Select(de => de.Key.ToString() + ": " + de.Value.ToString());
return string.Join("\n", valueStrings);
This basically "duck types" the existence of the Key and Value properties.
This sounds like it might be a case for the old System.Collections namespace:
private static string DictionaryToString(IDictionary dict) {
if (null == dict) throw new ArgumentNullException("dict");
var valueStrings = new List<string>();
foreach (DictionaryEntry item in dict) {
valueStrings.Add(item.Key + ": " + item.Value);
}
return string.Join("\n", valueStrings.ToArray());
}
private static string Test(object value) {
var dict = value as IDictionary;
if (dict != null) {
return DictionaryToString(dict);
}
if (value == null) {
return null;
}
return value.ToString();
}
private static void Main(string[] args) {
var aDictionary = new Dictionary<int, string> {
{ 1, "one" },
{ 2, "two" },
{ 3, "three" }
};
Console.WriteLine(Test(aDictionary));
var anotherDictionary = new Dictionary<string, object> {
{ "one", 1 },
{ "two", "2" },
{ "three", new object() }
};
Console.WriteLine(Test(anotherDictionary));
Console.ReadLine();
}
Reasoning:
The non-generic IDictionary will be a collection of key-value pairs in which the key is an object and the value is an object. All instances of object support ToString, so all keys and values of the collection can be converted to a string without knowing their specific types.
The reason why this does not work:
var castValue = value as Dictionary<,>
is because the generic type Dictionary<TKey, TValue> requires 2 type arguments. Without those type arguments, the collection is not generic. You'd be better off using the non-generic IDictionary if you do not know the key or value types at compile-time.
GetGenericTypeDefinition is available for previous versions http://msdn.microsoft.com/en-us/library/system.type.getgenerictypedefinition(v=vs.100).aspx. I think the 4.5 page on MSDN is missing the "Other versions" dropdown.
You could cast to dynamic in .NET 4.0. The prior typeof(Dictionary<,>) check will ensure that you won't get runtime errors.
var castValue = value as dynamic;
return DictionaryToString(castValue);
Why not?
if ((value !=null) && value.GetType().IsGenericType &&
value.GetType().GetGenericTypeDefinition() == typeof (Dictionary<,>))
{
return DictionaryToString(castValue);
}
else
{
return value.ToString();
}

Reflection help. Make a collection from a class based on its properties?

I need a little help. I am fairly new to reflection. We're using a 3rd party api and it returns a class called "AddressList". It has public properties within it literally called Address1, Address1Name, Address1Desc, Address2, Address2Name, Address2Desc, Address3, Address3Name, Address3Desc,... Address99, Address99Name, Address99Desc.. There are also a couple of other properties. I have a class called "SimpleAddress" that has just the 3 properties (Address, Name, Description). What I want to do is when I get the "AddressList" class returned, I would like to loop AddressDesc1... through AddressDesc99... and whichever ones are not null or empty, I would like to create an instance of "SimpleAddress", populate it's properties, and add it to a List... Can someone point me in the right direction? Obviously this would have been better if "AddressList" was some sort of collection, but unfortunately it is not. It is generated from a return string from a mainframe.
Thanks for any help,
~ck in San Diego
Ick. You could do something like this:
List<SimpleAddress> addresses = new List<SimpleAddress>();
string addressPropertyPattern = "Address{0}";
string namePropertyPattern = "Address{0}Name";
string descPropertyPattern = "Address{0}Desc";
for(int i = 1; i <= MAX_ADDRESS_NUMBER; i++)
{
System.Reflection.PropertyInfo addressProperty = typeof(AddressList).GetProperty(string.Format(addressPropertyPattern, i));
System.Reflection.PropertyInfo nameProperty = typeof(AddressList).GetProperty(string.Format(namePropertyPattern, i));
System.Reflection.PropertyInfo descProperty = typeof(AddressList).GetProperty(string.Format(descPropertyPattern, i));
SimpleAddress address = new SimpleAddress();
address.Address = (string)addressProperty.GetValue(yourAddressListObject, null);
address.Name = (string)nameProperty.GetValue(yourAddressListObject, null);
address.Description = (string)descProperty.GetValue(yourAddressListObject, null);
addresses.Add(address);
}
Start by getting the type of the class in question and invoke the GetProperties method.
PropertyInfo[] properties = myMainframeObject.GetType().GetProperties();
Each PropertyInfo has a Name attribute (a string) you can use to match against. Loop over all the properties, and write the code that creates a new instance of SimpleAddress.
Inside this loop, you can access your mainframe object and pull out the property values you need:
// imagine that in this case, 'p' is a PropertyInfo that represents Address2Name
var simpleAddress = new SimpleAddress();
simpleAddress.Name = p.GetValue(myMainframeObject, null);
(the null is never used for normal properties - it is intended for use with indexed properties).
You should be able to do something like:
List<SimpleAddress> CreateList(AddressList address)
{
List<SimpleAddress> values = new List<SimpleAddress>();
Type type = address.GetType();
for (int i=1;i<=99;++i)
{
string address = type.GetProperty("Address" + i.ToString()).GetValue(address,null).ToString();
string addressDesc = type.GetProperty("Address" + i.ToString() + "Desc").GetValue(address,null).ToString();
string addressName = type.GetProperty("Address" + i.ToString() + "Name").GetValue(address,null).ToString();
if (!string.IsNullOrEmpty(addressDesc) || !string.IsNullOrEmpty(addressName) || !string.IsNullOrEmpty(address) )
value.Add(new SimpleAddress(address,addressDesc,addressName));
}
return values;
}
Not tested (for obvious reasons), but something like:
List<SimpleAddress> newList = new List<SimpleAddress>();
AddressList list = ...
Type type = list.GetType();
PropertyInfo prop1, prop2, prop3;
int index = 1;
while((prop1 = type.GetProperty("Address" + index)) != null
&& (prop2 = type.GetProperty("Address" + index + "Name")) != null
&& (prop3 = type.GetProperty("Address" + index + "Desc")) != null) {
string addr = (string) prop1.GetValue(list, null),
name = (string) prop2.GetValue(list, null),
desc = (string) prop3.GetValue(list, null);
if(addr == null || name == null || desc == null) {
continue; // skip but continue
}
SimpleAddress newAddr = new SimpleAddress(addr, name, desc);
newList.Add(newAddr);
index++;
}
if you want to use linq
public static class MyTools
{
public static TReturn GetValue<TReturn>(this object input,
string propertyName)
{
if (input == null)
return default(TReturn);
var pi = input.GetType().GetProperty(propertyName);
if (pi == null)
return default(TReturn);
var val = pi.GetValue(input, null);
return (TReturn)(val == null ? default(TReturn) : val);
}
public static string GetString(this object input, string propertyName)
{
return input.GetValue<string>(propertyName);
}
public static List<SimpleAddress> GetAddress(this MyObject input)
{
return (
from i in Enumerable.Range(1, 2)
let address = input.GetString("Address" + i.ToString())
let name = input.GetString("Address" + i.ToString() + "Name")
let desc = input.GetString("Address" + i.ToString() + "Desc")
select new SimpleAddress() { Address = address,
Name = name,
Description = desc }
).ToList();
}
}
var addrList = new AddressList
{
Address1Name = "ABC",
Address1Desc = "DEF",
Address1 = "GHI",
Address3Name = "X",
Address3Desc = "Y",
Address3 = "Z"
};
var addresses =
from i in Enumerable.Range(1, 99)
let desc = typeof(AddressList).GetProperty(string.Format("Address{0}Desc", i)).GetValue(addrList, null) as string
let name = typeof(AddressList).GetProperty(string.Format("Address{0}Name", i)).GetValue(addrList, null) as string
let address = typeof(AddressList).GetProperty(string.Format("Address{0}", i)).GetValue(addrList, null) as string
where !string.IsNullOrEmpty(address)
select new SimpleAddress
{
Name = name,
Description = desc,
Address = address
};

Categories