I got the following structures:
public class Foo
{
public string Code { get; set; }
public string Description { get; set; }
public string Name { get; set; }
}
var fooMapping = GetFooMapping();
var fooObjects = GetFoos();
fooObjects.All(foo =>
{
Tuple<string, string> value = null;
if (fooMapping.TryGetValue(foo.Code, out value))
{
foo.Description = value.Item1;
foo.Name = value.Item2;
};
return true;
});
The GetFoos method returns an Enumerable of Foo objects that don't have all their properties set.
GetFooMapping returns an
IDictionary<string, Tuple<string, string>>
where TKey = Foo.Code and TValue.Item1 = Foo.Description and TValue.Item2 = Foo.Name.
While debugging, after running through the last lines of code I see that some of the properties weren't set even though the Foo.Code exists in the dictionary.
Am I missing something?
Instead of All you could use Select and assign the result back to fooObjects
fooObjects = fooObjects.Select(foo =>
{
Tuple<string, string> value = null;
if (fooMapping.TryGetValue(foo.Code, out value))
{
foo.Description = value.Item1;
foo.Name = value.Item2;
}
return foo;
});
The main issue that you are likely having is that All is iterating over an IEnumerable that is generating it's items. Then the next time you iterate fooObjects it generates them again and the changes in All are lost. Further this is not how Linq is meant to be used. All is meant for checking a predicate over the collection, not for modifying or projecting.
The other option is to make sure you are working with a list and just use a foreach
var fooObjects = GetFoos().ToList();
foreach(var foo in fooObjects)
{
Tuple<string, string> value = null;
if (fooMapping.TryGetValue(foo.Code, out value))
{
foo.Description = value.Item1;
foo.Name = value.Item2;
}
}
Related
I am trying to update a List which is a List of Interfaces to concrete classes.
I add to the List each Market type i am interested in, for this Example these Markets are A and B
I loop over all the markets, (sample provided with 3 markets A B & C, we are only interested in A and B) And determine which is of interest to us.
Once found we pass this to an extraction method too do its work and create an instance of the Correct Market_ class type.
This all works fine, but when i try to update the list with the Updates it does not get reflected in the List.
Code below, any Suggestions?
Thanks
public class Test
{
public Test()
{
TheMarkets MarketsToUpdate = new TheMarkets();
List<SpecificCompanyMarket> lstMarks = new List<SpecificCompanyMarket>();
lstMarks.Add(new SpecificCompanyMarket(1234, "A", "Some HTML DATA HERE"));
lstMarks.Add(new SpecificCompanyMarket(5874, "B", "Some HTML DATA HERE"));
lstMarks.Add(new SpecificCompanyMarket(2224, "C", "Some HTML DATA HERE"));
foreach (var item in lstMarks)
{
if (MarketsToUpdate.IsMarketWeAreInterestedIn(item.MarketName))
{
ITheMarkets MarkToUpdate = ExtractMarketData(item);
var obj = MarketsToUpdate.MarketsWeAreInterestedIn.FirstOrDefault(x => x.MarketName() == "A");
if (obj != null)
{
obj = MarkToUpdate;
}
}
}
//Look At MarketsToUpdate Now and the item has not changed, still original values
//I was expecting to see the new values for the fields in A, not the default 0's
}
public ITheMarkets ExtractMarketData(SpecificCompanyMarket item)
{
ITheMarkets market = null;
if (item.MarketName.ToUpper() == "A")
{
Market_A marketType = new Market_A();
marketType.SomeValue1 = 123;
marketType.SomeValue2 = 158253;
market = marketType;
}
//Other Market extractions here
return market;
}
}
public class SpecificCompanyMarket
{
public int MarketId { get; set; }
public string MarketName { get; set; }
public string MarketDataHTML { get; set; }
public SpecificCompanyMarket(int MID, string MName, string MData)
{
MarketId = MID;
MarketName = MName;
MarketDataHTML = MData;
}
}
public class TheMarkets
{
public List<ITheMarkets> MarketsWeAreInterestedIn = new List<ITheMarkets>();
public TheMarkets()
{
Market_A A = new Market_A();
Market_B B = new Market_B();
MarketsWeAreInterestedIn.Add(A);
MarketsWeAreInterestedIn.Add(B);
}
public bool IsMarketWeAreInterestedIn(string strMarketName)
{
bool blnRetVal = false;
foreach (var item in MarketsWeAreInterestedIn)
{
if (item.MarketName().ToUpper().Trim().Equals(strMarketName.ToUpper().Trim()))
{
blnRetVal = true;
break;
}
}
return blnRetVal;
}
}
public interface ITheMarkets
{
string MarketName();
}
public class Market_A : ITheMarkets
{
public string LabelType { get; private set; }
public double SomeValue1 { get; set; }
public double SomeValue2 { get; set; }
public double SomeValue3 { get; set; }
public Market_A()
{
LabelType = "A";
}
public string MarketName()
{
return LabelType;
}
}
public class Market_B : ITheMarkets
{
public string LabelType { get; private set; }
public List<string> SomeList { get; set; }
public double SomeValue { get; set; }
public Market_B()
{
LabelType = "B";
}
public string MarketName()
{
return LabelType;
}
}
This is a short example to get you going. Loop through your list, find the object you want to update, create a new object of that type and then find the original objects index in the list and overwrite it in place. You are essentially just replacing the object in the list with a new one not mutating the existing one.
foreach (var item in lstMarks)
{
//your code to get an object with data to update
var yourObjectToUpdate = item.GetTheOneYouWant();
//make updates
yourObjectToUpdate.SomeProperty = "New Value";
int index = lstMarks.IndexOf(item);
lstMarks[index] = yourObjectToUpdate;
}
You are extracting an obj from marketWeAreInterestedIn list using LINQ's firstOrDefault extension. This is a new object and not a reference to the obj in that list. Therefore, no updates will be reflected in the object inside that list. Try using 'indexof'
You are not storing "list of interfaces" in your list. List<T> stores an array of pointers to objects that support T interface. Once you enumerate (with Linq in your case) your list, you copy a pointer from list, which is not associated with list itself in any way. It is just a pointer to your instance.
To do what you want, you will have to build new list while enumerating the original one, adding objects to it, according to your needs, so the second list will be based on the first one but with changes applied that you need.
You can also replace specific instance at specific index instead of building new list in your code, but to do this you will need to enumerate your list with for loop and know an index for each item:
list[index] = newvalue;
But there is a third solution to update list item directly by Proxying them. This is an example
class ItemProxy : T { public T Value { get; set; } }
var list = new List<ItemProxy<MyClass>>();
list.Insert(new ItemProxy { Value = new MyClass() });
list.Insert(new ItemProxy { Value = new MyClass() });
list.Insert(new ItemProxy { Value = new MyClass() });
foreach(var item in list)
if(item // ...)
item.Value = new MyClass(); // done, pointer in the list is updated.
Third is the best case for perfomance, but it will be better to use this proxying class for something more than just proxying.
I am creating a Log Parsing tool, that is parsing a CSV File into individual classes that derive from a root class. However, it is taking a long time to define the individual classes and to set their individual properties in each class, since there is hundreds of different types of logs. The thing I did notice is that it is all pretty much exactly the same thing and wanted to see if there was a way to speed things up and do something along the lines of how LINQ to DB does things and add some logic in to automatically set properties based on information from Attributes.
Below is an example of what I am working with and an idea on how things should work.
class Program
{
static void Main(string[] args)
{
Dictionary<string, string> dictionary = new Dictionary<string, string>
{
{"key", "Stack Overflow"},
{"item1", "Test"},
{"item2", "Sample"},
{"item3", "3"}
};
Example example = new Example(dictionary);
Console.WriteLine(example.LogKey); //Stack Overflow
Console.WriteLine(example.Item1); //Test
Console.WriteLine(example.Item2); //
Console.WriteLine(example.Item3); //3
Console.ReadKey();
}
}
[AttributeUsage(AttributeTargets.Property)]
class LogItem : Attribute
{
public LogItem(string key)
{
Key = key;
}
public string Key { get; private set; }
public bool Ignore { get; set; }
}
class Log
{
public Log(Dictionary<string, string> items)
{
Dictionary = items;
}
public Dictionary<string, string> Dictionary { get; private set; }
[LogItem("key")]
public string LogKey { get; set; }
}
class Example : Log
{
public Example(Dictionary<string, string> items) : base(items)
{
}
[LogItem("item1")]
public string Item1 { get; set; }
[LogItem("item2", Ignore = true)]
public string Item2 { get; set; }
[LogItem("item3")]
public int Item3 { get; set; }
}
All of my data is comming through as a string unfortunately so it would be a good idea to get the type of the property and converting the string to that. Not important right now for this question since I can do that on my own.
Does anyone have an idea on how to make something like this work? If possible could something like this be done in the Parent Class to allow the Child Class to set the properties with the Attribute Ignore == true on it's own.
I was able to come up with the following after looking into how LINQ to CSV works.
static void ExtractData(Log log)
{
List<PropertyInfo> propertyInfos =
log.GetType()
.GetProperties()
.Where(
p => p.GetCustomAttributes(typeof (LogItem), true).Any(logItem => !((LogItem) logItem).Ignore))
.ToList();
foreach (var propertyInfo in propertyInfos)
{
LogItem logItem = (LogItem)propertyInfo.GetCustomAttributes(typeof(LogItem), true).First();
if(!log.Dictionary.ContainsKey(logItem.Key))
continue;
TypeConverter typeConverter = TypeDescriptor.GetConverter(propertyInfo.PropertyType);
MethodInfo parseNumberMethod = propertyInfo.PropertyType.GetMethod("Parse",
new[] { typeof(String), typeof(NumberStyles), typeof(IFormatProvider) });
MethodInfo parseExactMethod = propertyInfo.PropertyType.GetMethod("ParseExact",
new[] { typeof(string), typeof(string), typeof(IFormatProvider) });
Object objValue = null;
if (typeConverter.CanConvertFrom(typeof(string)))
{
objValue = typeConverter.ConvertFromString(null, CultureInfo.CurrentCulture, log.Dictionary[logItem.Key]);
Debug.WriteLine("TypeConverter - " + propertyInfo.Name);
}
else if (parseExactMethod != null)
{
objValue =
parseExactMethod.Invoke(
propertyInfo.PropertyType,
new Object[]
{
log.Dictionary[logItem.Key],
logItem.OutputFormat,
CultureInfo.CurrentCulture
});
}
else if (parseNumberMethod != null)
{
objValue =
parseNumberMethod.Invoke(
propertyInfo.PropertyType,
new Object[]
{
log.Dictionary[logItem.Key],
logItem.NumberStyles,
CultureInfo.CurrentCulture
});
}
else
{
objValue = log.Dictionary[logItem.Key];
}
PropertyInfo goodPropertyInfo = propertyInfo.DeclaringType.GetProperty(propertyInfo.Name);
goodPropertyInfo.SetValue(log, objValue, null);
}
}
Have you seen LinqToCSV? You create classes for each of the types of log, adding inheritance etc and describe the columns using attributes.
here's an example of how simple your code would become.
IEnumerable<ManualInputFormat> MapFileToRows(Stream input)
{
var csvDescriptor = new CsvFileDescription
{
SeparatorChar = ',',
FirstLineHasColumnNames = true
};
var context = new CsvContext();
return context.Read<InputFormat>(new StreamReader(input), csvDescriptor);
}
where InputFormat is your destination attribute decorated POCO
http://www.codeproject.com/Articles/25133/LINQ-to-CSV-library
This has been plaguing me for days now....
If I have a list of my own object SearchResults and SearchResults contains multiple lists of objects, all of which have a match (bool) property, How can I recreate an expression tree to achieve the following:
//searchResults is a List<SearchResults>
searchResults[i].Comments = searchResults[i].Comments.Select(p1 =>
{
p1.Match = ListOfStringVariable.All(p2 =>
{
string value = (string)typeof(CommentData).GetProperty(propertyName).GetValue(p1);
return value.Contains(p2);
});
return p1;
}).OrderByDescending(x => x.Match);
....
public class SearchResults
{
public IEnumerable<CommentData> Comments { get; set; }
public IEnumerable<AdvisorData> Advisors { get; set; }
}
public class CommentData
{
public string CommentText { get; set; }
public bool Match { get; set; }
}
public class AdvisorData
{
public string FirstName { get; set; }
public string LastName { get; set; }
public bool Match { get; set; }
}
The expression tree is needed as I won't know the property at compile-time that needs to be assigned, whether it is Comments, Advisors, etc (As this is a simplification of a larger problem). The above example is just for Comments, so how could the same code be used to assign to Advisors as well without having a conditional block?
Many thanks
Update:
So far using reflection we have the below from StriplingWarrior
var searchResult = searchResults[i];
foreach (var srProperty in searchResultsProperties)
{
var collectionType = srProperty.PropertyType;
if(!collectionType.IsGenericType || collectionType.GetGenericTypeDefinition() != typeof(IEnumerable<>))
{
throw new InvalidOperationException("All SearchResults properties should be IEnumerable<Something>");
}
var itemType = collectionType.GetGenericArguments()[0];
var itemProperties = itemType.GetProperties().Where(p => p.Name != "Match");
var items = ((IEnumerable<IHaveMatchProperty>) srProperty.GetValue(searchResult))
// Materialize the enumerable, in case it's backed by something that
// would re-create objects each time it's iterated over.
.ToList();
foreach (var item in items)
{
var propertyValues = itemProperties.Select(p => (string)p.GetValue(item));
item.Match = propertyValues.Any(v => searchTerms.Any(v.Contains));
}
var orderedItems = items.OrderBy(i => i.Match);
srProperty.SetValue(srProperty, orderedItems);
}
However orderedItems is of type System.Linq.OrderedEnumerable<IHaveMatchProperty,bool> and needs to be cast to IEnumerable<AdvisorData>. The below throws error:
'System.Linq.Enumerable.CastIterator(System.Collections.IEnumerable)' is a 'method' but is used like a 'type'
var castMethod = typeof(Enumerable).GetMethod("Cast").MakeGenericMethod(new[] {propertyType});
var result = castMethod.Invoke(null, new[] { orderedItems });
where propertyType is type AdvisorData
First, make your types implement this interface so you don't have to do quite so much reflection:
public interface IHaveMatchProperty
{
bool Match { get; set; }
}
Then write code to do something like this. (I'm making a lot of assumptions because your question wasn't super clear on what your intended behavior is.)
var searchResult = searchResults[i];
foreach (var srProperty in searchResultsProperties)
{
var collectionType = srProperty.PropertyType;
if(!collectionType.IsGenericType || collectionType.GetGenericTypeDefinition() != typeof(IEnumerable<>))
{
throw new InvalidOperationException("All SearchResults properties should be IEnumerable<Something>");
}
var itemType = collectionType.GetGenericArguments()[0];
var itemProperties = itemType.GetProperties().Where(p => p.Name != "Match");
var items = ((IEnumerable<IHaveMatchProperty>) srProperty.GetValue(searchResult))
// Materialize the enumerable, in case it's backed by something that
// would re-create objects each time it's iterated over.
.ToList();
foreach (var item in items)
{
var propertyValues = itemProperties.Select(p => (string)p.GetValue(item));
item.Match = propertyValues.Any(v => searchTerms.Any(v.Contains));
}
var orderedItems = items.OrderBy(i => i.Match);
srProperty.SetValue(srProperty, orderedItems);
}
I have a Dictionary which is of type,
Dictionary<string, string> newdictionary = new Dictionary<string, string>();
newdictionary.Add("12345", "chip1");
newdictionary.Add("23456", "chip2");
Now i have a List which is of type
internal class CustomSerial
{
public string SerialNo { get; set; }
public decimal ecoID { get; set; }
}
var customList = new List<CustomSerial>();
CustomSerial custObj1= new CustomSerial();
custObj1.ecoID =1;
custObj1.SerialNo = "12345";
customList.Add(custObj1);
CustomSerial custObj2 = new CustomSerial();
custObj2.ecoID = 2;
custObj2.SerialNo = "23456";
customList.Add(custObj2);
Now i need to update the Initial dictionary by Filtering the Keys with ther SerialNumber and Replacing the values with the ecoID.
When i try this, it gives
foreach (KeyValuePair<string, string> each in newdictionary)
{
each.Value = customList.Where(t => t.SerialNo == each.Key).Select(t => t.ecoID).ToString();
}
System.Collections.Generic.KeyValuePair.Value' cannot be assigned to -- it is read only
LIN(Q) is a tool to query something not to update it.
However, you can first query what you need to update. For example:
var toUpdate = customList
.Where(c => newdictionary.ContainsKey(c.SerialNo))
.Select(c => new KeyValuePair<string, string>(c.SerialNo, c.ecoID.ToString()));
foreach(var kv in toUpdate)
newdictionary[kv.Key] = kv.Value;
By the way, you get the "KeyValuePair.Value' cannot be assigned to it is read only" exception because aKeyValuePair<TKey, TValue> is a struct which cannot be modified.
You'd have the simplest in this form: though I don't see why you are assigning the same value but the method applies regardless
var dictionary = new Dictionary<string, string>() { { "12345", "chip1" }, { "23456", "chip2" } };
var customList = new List<CustomSerial>() { new CustomSerial() { ecoID = 1, SerialNo = "12345" }, new CustomSerial() { ecoID = 2, SerialNo = "23456" } };
dictionary.Keys.ToList().ForEach(key =>
{
dictionary[key] = customList.FirstOrDefault(c => c.SerialNo == key).SerialNo;
});
I ran into a problem I cannot solve. :-) I have to find all the values which contains a certain substring then I must get back the key and value pair. I had to implement a system where I had to make a SortedList, where the Albums is a class string is the key of course
Albums alb = new Albums();
SortedList<string, Albums> list1 = new SortedList<string, Albums>();
The Albums class looks like this:
public class Albums : IComparable
{
public string albname;
public string name1;
public string releasedate;
public Guid albumID;
public Albums()
{
albumID = new Guid();
}
public override string ToString()
{
return "Album: " + Albname + "\t" + Releasedate;
}
public Albums(string albname, string releasedate)
{
this.albname = albname;
this.releasedate = releasedate;
}
public string Name1
{
get { return name1; }
set { name1 = value; }
}
public string Albname
{
get { return albname; }
set { albname = value; }
}
public string Releasedate
{
get { return releasedate; }
set { releasedate = value; }
}
public int CompareTo(object obj)
{
if (obj is Albums)
{
Albums other = (Albums)obj;
return albname.CompareTo(other.albname);
}
if (obj is string)
{
string other = (string)obj;
return releasedate.CompareTo(releasedate);
}
else
{
return -999;
}
}
}
What I tried at last that I put the Albums into a LinkedList:
LinkedList<string> albm1 = new LinkedList<string>();
I did manage to find all the Albums that contains the substring using IEnumerable:
string albsearch = textBox16.Text;
IEnumerable<string> result = albm1.Where(s => s.Contains(albsearch));
BUT I do not know how to compare result to the Values of the SortedList. I also tried to create a new SortedList which contains the album in string:
SortedList<string, string> list2 = new SortedList<string, string>();
Any suggestion would be appreciated. Thanks in advance!
When you enumerate a SortedList, each item in the enumeration is a key/value pair.
I think what you want is:
Albums alb = new Albums();
SortedList<string, Albums> list1 = new SortedList<string, Albums>();
var foundItems = list1.Where(item => item.Key.Contains(albsearch));
Or, if you want to search in the Album:
var foundItems = list1.Where(item => item.Value.albname.Contains(albsearch));
Of course, you could search the name1 field rather than the album name, if that's what you want.
Each item returned is a key/value pair. More correctly, a KeyValuePair<string, Album>.
[Test]
public void Allbum_Search_Test()
{
var searchText = "TestA";
var list1 = new SortedList<string, Albums>
{
{"TestAlbum1", new Albums("TestAlbum1","SomeDate")},
{"TestAlbum2", new Albums("TestAlbum2","SomeDate")},
{"OtherAlbum2", new Albums("OtherAlbum","SomeDate")}
};
var results = list1.Where(pair => pair.Key.Contains(searchText));
Assert.That(results.Count(), Is.EqualTo(2));
}
On another note i would highly recommend
making the fields private
renaming Albums to Album
changing the ReleaseDate type to DateTime
renaming Albname to AlbumName