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

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

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();

Find next string in a list of string

I have a list of UserNames in a comma delimited string. I want to find next one of the input username.
For Example:
var s0 = "abc,deF,ghi,jkl";
var s1 = "abc";
var s2 = "def";
var s3 = "ghi";
var s4 = "jkl";
Result should be:
NextInString(s0,s1 ) == "def"
NextInString(s0,s2 ) == "ghi"
NextInString(s0,s3 ) == "jkl"
NextInString(s0,s4 ) == "jkl"
Here is what I have:
string NextInString(string listOfNames, string userName)
{
if(listOfNames == string.Empty || userName == string.Empty)
return string.Empty;
var s = listOfNames.Split(',');
var count = 0;
foreach (var element in s)
{
if (element == userName)break;
count++;
}
if (s.Length -1 == count)
{
return s[count];
}
else return s[ count + 1 ];
}
My question is, is there a better/easier way to approach this?
If you take the extra step to ensure your string list is trimmed, you can just use the IndexOf() method of List<T>:
string csv = "test1, test2, test3, test4";
List<string> names = csv.Split(',').Select(x => x.Trim()).ToList();
Then your NextInString() method (I think this is a poorly named method) would look like this:
private static string NextInString(List<string> names, string userName)
{
int index = names.IndexOf(userName);
if(names.Count - 1 == index || index == -1)
{
return "No result";
}
else
{
return names[index + 1];
}
}
I made a fiddle here
You can use Linq like this:
string userName = "abc";
string listOfNames = "abc,xyz,123";
var names = listOfNames
.Split(',')
.Select((n, i) => new {name = n, index =i} )
.ToArray();
var firstMatch = names.FirstOrDefault(n => n.name == userName);
var result = firstMatch == null
? string.Empty
: firstMatch.index == names.Length - 1
? string.Empty
: names[firstMatch.index + 1].name;
Here is the LINQ approach:
string NextInString(string listOfNames, string userName)
{
if(listOfNames == string.Empty || userName == string.Empty) return string.Empty;
var names = listOfNames.Split(',');
return names
.SkipWhile(x => x != userName)
.Skip(1)
.FirstOrDefault() ?? names.Last();
}
You can make a nice little extension method to do this after the string is split, like so:
static class IListExtensions
{
public static T FindItemAfter<T>(this IList<T> list, T targetItem)
{
return list[list.IndexOf(targetItem)+ 1];
}
}
You can use it like this:
static void Main(string[] args)
{
var list = "cat,dog,rat".Split(',');
Console.WriteLine(list.FindItemAfter("cat"));
Console.WriteLine(list.FindItemAfter("dog"));
Console.ReadLine();
}
It returns:
dog
rat
This overload will allow you to specify a default value that gets returned if the requested item isn't found, or the next item would be outside the list.
public static T FindItemAfter<T>(this IList<T> list, T targetItem, T defaultValue)
{
var index = list.IndexOf(targetItem);
if (index == -1 || index >= list.Count - 1)
{
return defaultValue;
}
return list[index + 1];
}
How about something like this?
var s = listOfNames.Split(',');
for (var i = 0; i < s.count; i++)
{
if (i == s.count - 1)
{
return string.Format("No user after {0} was found", userName);
}
else if (s[i] == userName)
{
return s[i + 1];
}
}

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)

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

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

ToString() of copied NameValueCollection doesn't output desired results

I have a NameValueCollection in a usercontrol that is initialized like so:
private NameValueCollection _nameValues = HttpUtility.ParseQueryString(Request.QueryString.ToString());
When I call the ToString() on this it generates a proper querystring which I can use for an updated url.
However, when I copy the NameValueCollection via its constructor like so:
var nameValues = new NameValueCollection(_nameValues);
And then try to form an url:
var newUrl = String.Concat(_rootPath + "?" + nameValues.ToString());
It outputs an url like this:
"http://www.domain.com?System.Collections.Specialized.NameValueCollection"
How can I copy a NameValueCollection so that the ToString() method outputs desired results?
The problem is there are two actual types in your code. The fist one is System.Web.HttpValueCollection which has it's ToString method overriden to get the result you expect and the second one is System.Collection.Specialized.NameValueCollection which does not override ToString. What you can do, if you really need to use System.Collection.Specialized.NameValueCollection is to create an extension method.
public static string ToQueryString(this NameValueCollection collection)
{
var array = (from key in collection.AllKeys
from value in collection.GetValues(key)
select string.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(value))).ToArray();
return "?" + string.Join("&", array);
}
and use it:
var newUrl = String.Concat(_rootPath,nameValues.ToQueryString());
It is not NameValueCollection that provides the string formatting. That functionality is in an internal class System.Web.HttpValueCollection that is returned by HttpUtility.ParseQueryString.
So you will not be able to achieve this behavior by using built in functionality. Your best bet would be to create an extension method that formats the values in a URL format.
Here is the method from HttpValueCollection class - you might be able to use it with some modifications.
// System.Web.HttpValueCollection
internal virtual string ToString(bool urlencoded, IDictionary excludeKeys)
{
int count = this.Count;
if (count == 0)
{
return string.Empty;
}
StringBuilder stringBuilder = new StringBuilder();
bool flag = excludeKeys != null && excludeKeys["__VIEWSTATE"] != null;
for (int i = 0; i < count; i++)
{
string text = this.GetKey(i);
if ((!flag || text == null || !text.StartsWith("__VIEWSTATE", StringComparison.Ordinal)) && (excludeKeys == null || text == null || excludeKeys[text] == null))
{
if (urlencoded)
{
text = HttpValueCollection.UrlEncodeForToString(text);
}
string value = (text != null) ? (text + "=") : string.Empty;
string[] values = this.GetValues(i);
if (stringBuilder.Length > 0)
{
stringBuilder.Append('&');
}
if (values == null || values.Length == 0)
{
stringBuilder.Append(value);
}
else
{
if (values.Length == 1)
{
stringBuilder.Append(value);
string text2 = values[0];
if (urlencoded)
{
text2 = HttpValueCollection.UrlEncodeForToString(text2);
}
stringBuilder.Append(text2);
}
else
{
for (int j = 0; j < values.Length; j++)
{
if (j > 0)
{
stringBuilder.Append('&');
}
stringBuilder.Append(value);
string text2 = values[j];
if (urlencoded)
{
text2 = HttpValueCollection.UrlEncodeForToString(text2);
}
stringBuilder.Append(text2);
}
}
}
}
}
return stringBuilder.ToString();
}
internal static string UrlEncodeForToString(string input)
{
return HttpUtility.UrlEncodeUnicode(input);
}
Calling .ToString() on a name value collection will just give you the namespace it belongs to.
I suspect you want the key and value out of it, Assuming that it's the first in the collection why not just do:
var newUrl = String.Concat(_rootPath + "?" + nameValues.GetKey(0) + nameValues.Get(0));
You can have this as an extension method:
public static string ToString(this NameValueCollection nvc, int idx)
{
if(nvc == null)
throw new NullReferenceException();
string key = nvc[idx];
if(nvc.HasKeys() && !string.IsNullOrEmpty(key))
{
return string.Concat(key, nvc.Get(key)); //maybe want some formatting here
}
return string.Empty;
}
Usage:
NameValueCollection nvc = new NameValueCollection();
string foo = nvc.ToString(0); //gets key + value at index 0

Categories