C# LINQ question about foreach - c#

is there any way to write this foreach in linq or another better way,
int itemNr = -1;
foreach(ItemDoc itemDoc in handOverDoc.Assignment.Items) {
itemNr++;
foreach(ItemDetailDoc detail in itemDoc.Details) {
int eventDocNr = -1;
foreach(EventDoc eventDoc in detail.Events) {
eventDocNr++;
if(!eventDoc.HasEAN) {
HideShowPanels(pMatch);
txt_EAN.Text = String.Empty;
lbl_Match_ArtName.Text = itemDoc.Name;
lbl_ArtNr.Text = itemDoc.Number;
lbl_unitDesc.Text = eventDoc.Description;
m_tempItemNr = itemNr;
m_tempEventNr = eventDocNr;
txt_EAN.Focus();
return;
}
}
}
}
I just think this is not the correct way to write it. please advise.

If itemNr and eventDocNr is not needed you could use:
var item =
(from itemDoc in handOverDoc.Assignment.Items
from detail in itemDoc.Details
from eventDoc in detail.Events
where !eventDoc.HasEAN
select new
{
Name = itemDoc.Name,
Number = itemDoc.Number,
Description = eventDoc.Description
}).FirstOrDefault();
if (item != null)
{
HideShowPanels(pMatch);
txt_EAN.Text = String.Empty;
lbl_Match_ArtName.Text = item.Name;
lbl_ArtNr.Text = item.Number;
lbl_unitDesc.Text = item.Description;
txt_EAN.Focus();
}

No, I dont think there is a better way to do that. LINQ is about queries, you do quite a lot of processing in there. Unless you have a shortcut that is not obvious here.... this seems t obe the only way.
If you COULD start from the eventDoc - you could filter out those without EAN and then go from there backward, but Ican not say how feasible that is as I miss the complete model (as in: maybe you have no back lniks, so you would be stuck wit hthe eventDoc an dcoul dnot get up to the item.
First look that looks fine.

You could try the following LINQ:
var nonEANs = from ItemDoc itemDocs in itemDocList
from ItemDetailDoc itemDetailDocs in itemDocs.Details
from EventDoc eventDocs in itemDetailDocs.Events
where !eventDocs.HasEAN
select eventDocs;
foreach (var i in nonEANs)
{
System.Diagnostics.Debug.WriteLine( i.HasEAN);
}
Should return 7 false EANs: I recreated you nested structures like this
List<ItemDoc> itemDocList = new List<ItemDoc>()
{
new ItemDoc()
{
Details = new List<ItemDetailDoc>()
{
new ItemDetailDoc()
{
Events = new List<EventDoc>()
{
new EventDoc()
{HasEAN=false},
new EventDoc()
{HasEAN=false}
}
},
new ItemDetailDoc()
{
Events = new List<EventDoc>()
{
new EventDoc()
{HasEAN=true},
new EventDoc()
{HasEAN=false}
}
}
}
},
new ItemDoc()
{
Details = new List<ItemDetailDoc>()
{
new ItemDetailDoc()
{
Events = new List<EventDoc>()
{
new EventDoc()
{HasEAN=false},
new EventDoc()
{HasEAN=false}
}
},
new ItemDetailDoc()
{
Events = new List<EventDoc>()
{
new EventDoc()
{HasEAN=false},
new EventDoc()
{HasEAN=false}
}
}
}
}
};

I think you are stuck with the for each loops as you need the itemNr and eventDocNr. You can use for loops to avoid increasing the itemNr and eventDocNr, but this does not reduce the number of loops.
Edit:
And if you do need the itemNr and eventDocNr try this:
var query = handOverDoc.Assignment.Items
.SelectMany(
(x, i) => x.Details.SelectMany(
(d, di) => d.Events.Where(x => x.HasEAN).Select(
(e, ei) => new {
ItemIndex = di,
EventIndex = ei,
Detail = d,
Event = e
}
)
)
);
foreach (var eventInfo in query) {
HideShowPanels(pMatch);
txt_EAN.Text = String.Empty;
lbl_Match_ArtName.Text = eventInfo.Detail.Name;
lbl_ArtNr.Text = eventInfo.Detail.Number;
lbl_unitDesc.Text = eventInfo.Event.Description;
txt_EAN.Focus();
return;
}
If you need only the first event with an EAN you could also use the following on the above query:
var item = query.FirstOrDefault();
if (item != null) {
// do you stuff here
}

You can get the index in LINQ quite easily, for example :-
var itemDocs = handOverDoc.Assignment.Items.Select((h, i) => new { item = h, itemindex = i })
You can repeat this process for your inner loops also and I suspect you could then use SelectMany() to simplify it even further.

You're trying to do two different things here. Firstly you're trying to find a document, and secondly you're trying to change things based upon it. The first stage in the process is simply to clarify the code you already have, e.g.
(Note this takes into account previous comments that the computed indexes in the original code are not needed. The exact same type of split into two methods could be done whether or not the computed indexes are required, and it would still improve the original code.)
public void FindAndDisplayEventDocWithoutEAN(HandOverDoc handOverDoc)
{
var eventDoc = FindEventDocWithoutEAN(handOverDoc);
if (eventDoc != null)
{
Display(eventDoc);
}
}
public EventDoc FindEventDocWithoutEAN(HandOverDoc handOverDoc)
{
foreach(ItemDoc itemDoc in handOverDoc.Assignment.Items)
foreach(ItemDetailDoc detail in itemDoc.Details)
foreach(EventDoc eventDoc in detail.Events)
if(!eventDoc.HasEAN)
return eventDoc;
return null;
}
public void Display(EventDoc eventDoc)
{
HideShowPanels(pMatch);
txt_EAN.Text = String.Empty;
lbl_Match_ArtName.Text = itemDoc.Name;
lbl_ArtNr.Text = itemDoc.Number;
lbl_unitDesc.Text = eventDoc.Description;
m_tempItemNr = itemNr;
m_tempEventNr = eventDocNr;
txt_EAN.Focus();
}
Once you've done that, you should be able to see that one method is a query over the main document, and the other is a method to display the results of the query. This is what's known as the single responsibility principle, where each method does one thing, and is named after what it does.
The transformation of the nested foreach loops to a linq query is now almost trivial. Compare the method below with the method above, and you can see how mechanical it is to translate nested foreach loops into a linq query.
public EventDoc FindEventDocWithoutEAN(HandOverDoc handOverDoc)
{
return (from itemDoc in handOverDoc.Assignment.Items
from detail in itemDoc.Details
from eventDoc in detail.Events
where !eventDoc.HasEAN
select eventDoc).FirstOrDefault();
}

yet another spin...
var query = from itemDocVI in handOverDoc.Assignment
.Items
.Select((v, i) => new { v, i })
let itemDoc = itemDocVI.v
let itemNr = itemDocVI.i
from detail in itemDoc.Details
from eventDocVI in detail.Events
.Select((v, i) => new { v, i })
let eventDoc = eventDocVI.v
let eventDocNr = eventDocVI.i
where eventDoc.HasEAN
select new
{
itemDoc,
itemNr,
detail,
eventDoc,
eventDocNr
};
var item = query.FirstOrDefault();
if (item != null)
{
HideShowPanels(pMatch);
txt_EAN.Text = String.Empty;
lbl_Match_ArtName.Text = item.itemDoc.Name;
lbl_ArtNr.Text = item.itemDoc.Number;
lbl_unitDesc.Text = item.eventDoc.Description;
m_tempItemNr = item.itemNr;
m_tempEventNr = item.eventDocNr;
txt_EAN.Focus();
}

Related

How to assign "var" inside if statement

I need to do this:
var productsLocation = response.blah blah; //with linq query
var item; // even var item = null; //not valid
if(condition){
item = productsLocation.linq query
} else {
item = productsLocation.differentquery
}
var group = item.query;
Is this possible? If yes, how?
EDIT: here is my exact code:
var productLocation = response.productLocation.Select(p => ProductLocationStaticClass.DtoToModel(p));
var items;
if (condition)
{
items = productLocation.Select(s => new ProductClass(s)).Where(s => categories.Contains(s.CategoryName));
} else {
items = productLocation.Select(s => new ProductClass(s)).Where(s => categories.Contains(s.CategoryName) && stocks.Contains(s.Barcode));
}
If you look closely at the logic, you notice you don't actually even need the if block. The whole thing can be written in one expression as follows:
var items = productLocation
.Select(s => new ProductClass(s))
.Where(s => categories.Contains(s.CategoryName) && (condition || stocks.Contains(s.Barcode)));
First of all get your response variable type, then initialize 'item' variable as IEnumarable where T is same as response variable type
var productsLocation = response.productLocation.Select(p => ProductLocationStaticClass.DtoToModel(p));
IEnumerable<ProductClass> item;
if (condition)
{
items = productLocation.Select(s => new ProductClass(s)).Where(s => categories.Contains(s.CategoryName));
}
else
{
items = productLocation.Select(s => new ProductClass(s)).Where(s => categories.Contains(s.CategoryName) && stocks.Contains(s.Barcode));
}
var group = item.Where(condition);
You can do it with IEnumerable interface in this way:
using System.Collections;
using System.Collections.Generic;
List<string> products = new List<string>() { "First", "Second", "Third", "Fourth" };
IEnumerable item;
var condition = false;
if (condition)
{
item = products.Select(x=>x);
}
else
{
item = products.Where(x => x.StartsWith("F"));
}
var group = item.Cast<string>().Where(/*...Here your conditions...*/)

Merging lists with a for loop

I'm working on an algorithm which can generate 2 types of recommendations, restaurants and dishes. All of this works fine, but I wanted to merge these 2 types of recommendations in a single list, which is where I encountered some issues. From my previous question I concluded that I needed a wrapper class, which I have set up like this:
public class RecommenderItem
{
public Guid Id { get; set; }
public object Entity { get; set; }
}
Now I want to alternate the 2 types of recommendations so the list would look like this:
[Restaurant][Dish][Restaurant][Dish][Restaurant][Dish] //Etc...
Note that these recommendations are completely separate. They are generated purely based on the user's preference, and they have no correlation in between them. My product owner wants to show these recommendations on the home page of our app like this.
These lists are different in length, so if I have added all items from a list, I wanted to just add the remaining objects from the other list. A possible scenario of this could look like this:
/*Other objects before this...*/[Dish][Restaurant][Dish][Dish][Dish] //Etc...
Here did the list of restaurant objects run out and I just wanted to add the remaining dish recommendations at the end of the list.
I have gotten this far, but I'm unsure how I would catch an IndexOutOfBounds exception and add the rest of the remaining objects at the end.
public List<RecommenderItem> GetMergedRecommendationLists(List<Restaurant> restaurantRecommendations,
List<Dish> dishRecommendations)
{
//Setting up the output list.
List<RecommenderItem> output = new List<RecommenderItem>();
int count = 0;
//Check which list is longer and use that count
if (restaurantRecommendations.Count > dishRecommendations.Count)
count = dishRecommendations.Count;
else
count = restaurantRecommendations.Count;
for (int i = 0; i < count; i++)
{
//I'm fully aware this isn't the most optimal way of doing this,
//but I'm only looking at functionality here, optimizing performance comes later.
var restRecommendation = restaurantRecommendations[i];
var dishRecommendation = dishRecommendations[i];
output.Add(new RecommenderItem()
{
Id = restRecommendation.Id,
Entity = restRecommendation
});
output.Add(new RecommenderItem()
{
Id = dishRecommendation.Id,
Entity = dishRecommendation
});
}
return output;
}
Does anyone have an idea how I could do this? Could I just catch an IndexOutOfBounds exception and use .AddRange() for the remaining objects? I'm not sure how I could check which list was out of bounds.
Let me know if I should elaborate more and thanks in advance!
Edit: -removed because it wasn't fair.-
This is a fairly succinct way of doing this.
While not Linq, it works in the spirit of the way Linq works by deferring doing any work until the resulting sequence is enumerated:
public static IEnumerable<RecommenderItem> Merge(IEnumerable<Restaurant> restaurants, IEnumerable<Dish> dishes)
{
using (var r = restaurants.GetEnumerator())
using (var d = dishes.GetEnumerator())
{
while (true)
{
bool rAvailable = r.MoveNext();
bool dAvailable = d.MoveNext();
if (rAvailable)
yield return new RecommenderItem { Id = r.Current.Id, Entity = r.Current };
if (dAvailable)
yield return new RecommenderItem { Id = d.Current.Id, Entity = d.Current };
if (!rAvailable && !dAvailable)
break;
}
}
}
If you happen to be using the MoreLinq NuGet package that includes the ZipLongest extension method, you can use the following simplified implementation instead:
public static IEnumerable<RecommenderItem> Merge(IEnumerable<Restaurant> restaurants, IEnumerable<Dish> dishes)
{
foreach (var item in restaurants.ZipLongest(dishes, (r, d) => new { r, d }))
{
if (item.r != null)
yield return new RecommenderItem { Id = item.r.Id, Entity = item.r };
if (item.d != null)
yield return new RecommenderItem { Id = item.d.Id, Entity = item.d };
}
}
Addendum
As #InBetween posted in his answer, you can put the interleaving logic into an extension method. Here's my version; it's substantially the same, except I've added a small optimisation to avoid calling .MoveNext() when its not necessary:
public static class EnumerableExt
{
public static IEnumerable<T> Interleave<T>(this IEnumerable<T> a, IEnumerable<T> b)
{
using (var ae = a.GetEnumerator())
using (var be = b.GetEnumerator())
{
bool aAvailable = true;
bool bAvailable = true;
while (aAvailable || bAvailable)
{
aAvailable = aAvailable && ae.MoveNext();
bAvailable = bAvailable && be.MoveNext();
if (aAvailable)
yield return ae.Current;
if (bAvailable)
yield return be.Current;
}
}
}
}
Once you have that, I realised that you don't need to write an implict operator. Instead, you can just convert the two sequences to the resultant type before calling Interleave() like so:
var restaurantsAsRecommenderItems =
restaurantRecommendations
.Select(r => new RecommenderItem {Id = r.Id, Entity = r});
var dishesAsRecommenderItems =
dishRecommendations
.Select(d => new RecommenderItem {Id = d.Id, Entity = d});
var result =
restaurantsAsRecommenderItems
.Interleave(dishesAsRecommenderItems)
.ToList();
My recommendation would be to just make simple implicit operator :
public static implicit operator RecommenderItem(Restaurant restaurant) {
return new RecommenderItem { Id = restaurant.Id, Entity = restaurant };
}
Then you have possibility to convert these types easily like :
Restaurant rest = //...
RecommenderItem rItem = rest; // here the implicit operator is called
After doing this you can just use one for loop :
int count = Math.Max(restaurantRecommendations.Count, dishRecommendations.Count);
for ( int i = 0; i < count; i++ ) {
if ( i < restRecommendations.Count )
output.Add(restRecommendations[i]);
if ( i < dishRecommendations.Count )
output.Add(dishRecommendations[i]);
}
This will make your work much more easier.
Well, there are probably more elegant LINQ solutions but you have already most, it's also a very efficient approach:
public List<RecommenderItem> GetMergedRecommendationLists(List<Restaurant> restaurantRecommendations, List<Dish> dishRecommendations)
{
//Setting up the output list.
List<RecommenderItem> output = new List<RecommenderItem>();
int count = Math.Min(restaurantRecommendations.Count, dishRecommendations.Count);
for (int i = 0; i < count; i++)
{
var restRecommendation = restaurantRecommendations[i];
var dishRecommendation = dishRecommendations[i];
output.Add(new RecommenderItem()
{
Id = restRecommendation.Id,
Entity = restRecommendation
});
output.Add(new RecommenderItem()
{
Id = dishRecommendation.Id,
Entity = dishRecommendation
});
}
int remainingRestaurant = restaurantRecommendations.Count - count;
int remainingDishes = dishRecommendations.Count - count;
if (remainingRestaurant > 0)
{
for (int i = count; i < restaurantRecommendations.Count; i++)
{
var restRecommendation = restaurantRecommendations[i];
output.Add(new RecommenderItem()
{
Id = restRecommendation.Id,
Entity = restRecommendation
});
}
}
else if (remainingDishes > 0)
{
for (int i = count; i < dishRecommendations.Count; i++)
{
var dishRecommendation = dishRecommendations[i];
output.Add(new RecommenderItem()
{
Id = dishRecommendation.Id,
Entity = dishRecommendation
});
}
}
return output;
}
A simple way of doing it would be:
public static IEnumerable<T> Merge<T>(this IEnumerable<T> first, IEnumerable<T> second)
{
using (var firstEnumerator = first.GetEnumerator())
using (var secondEnumerator = second.GetEnumerator())
{
while (firstEnumerator.MoveNext())
{
yield return firstEnumerator.Current;
if (secondEnumerator.MoveNext())
{
yield return secondEnumerator.Current;
}
}
while (secondEnumerator.MoveNext())
{
yield return secondEnumerator.Current;
}
}
}
After having created two arrays of restaurants and dishes of the same type RecommenderItem, you can use the Zip method like :
var restaurants = restaurantRecommendations.Select(x => new RecommenderItem {
Id = x.Id,
Entity = x
}).ToArray();
var dishes = dishRecommendations.Select(x => new RecommenderItem {
Id = x.Id,
Entity = x
}).ToArray();
var output = restaurants.Zip(dishes, (r, d) => new[] { r, d })
.SelectMany(r => r).Concat(dishes.Skip(restaurants.Length))
.Concat(restaurants.Skip(dishes.Length));
Restaraunt and Dish would have to share a base type:
restaurantRecommendations.Select(item => new RecommenderItem()
{
Id = item.Id,
Entity = item
});
dishRecommendations.Select(item => new RecommenderItem()
{
Id = item.Id,
Entity = item
});
Once that's the case you could use something like this slightly modified version of Zip (from System.Linq):
private static IEnumerable<T> ZipThrough<T>(IEnumerable<T> first, IEnumerable<T> second)
{
if (first == null) throw new ArgumentNullException(nameof(first));
if (second == null) throw new ArgumentNullException(nameof(second));
using (var e1 = first.GetEnumerator())
{
using (var e2 = second.GetEnumerator())
{
while (true)
if (e1.MoveNext())
{
yield return e1.Current;
if (e2.MoveNext()) yield return e2.Current;
}
else if (e2.MoveNext())
{
yield return e2.Current;
}
else
{
break;
}
}
}
}

Finding all identifiers containing part of the token

I know I can get a string from resources using
Resources.GetIdentifier(token, "string", ctx.ApplicationContext.PackageName)
(sorry, this is in C#, it's part of a Xamarin.Android project).
I know that if my elements are called foo_1, foo_2, foo_3, then I can iterate and grab the strings using something like
var myList = new List<string>();
for(var i = 0; i < 4; ++i)
{
var id = AppContent.GetIdentifier(token + i.ToString(), "string", "package_name");
if (id != 0)
myList.Add(AppContext.GetString(id));
}
My issue is that my token names all begin with "posn." (the posn can denote the position of anything, so you can have "posn.left_arm" and "posn.brokenose"). I want to be able to add to the list of posn elements, so I can't really store a list of the parts after the period. I can't use a string-array for this either (specific reason means I can't do this).
Is there a way that I can use something akin to "posn.*" in the getidentifer call to return the ids?
You can use some reflection foo to get what you want. It is not pretty at all but it works. The reflection stuff is based on https://gist.github.com/atsushieno/4e66da6e492dfb6c1dd0
private List<string> _stringNames;
private IEnumerable<int> GetIdentifiers(string contains)
{
if (_stringNames == null)
{
var eass = Assembly.GetExecutingAssembly();
Func<Assembly, Type> f = ass =>
ass.GetCustomAttributes(typeof(ResourceDesignerAttribute), true)
.OfType<ResourceDesignerAttribute>()
.Where(ca => ca.IsApplication)
.Select(ca => ass.GetType(ca.FullName))
.FirstOrDefault(ty => ty != null);
var t = f(eass) ??
AppDomain.CurrentDomain.GetAssemblies().Select(ass => f(ass)).FirstOrDefault(ty => ty != null);
if (t != null)
{
var strings = t.GetNestedTypes().FirstOrDefault(n => n.Name == "String");
if (strings != null)
{
var fields = strings.GetFields();
_stringNames = new List<string>();
foreach (var field in fields)
{
_stringNames.Add(field.Name);
}
}
}
}
if (_stringNames != null)
{
var names = _stringNames.Where(s => s.Contains(contains));
foreach (var name in names)
{
yield return Resources.GetIdentifier(name, "string", ComponentName.PackageName);
}
}
}
Then somewhere in your Activity you could do:
var ids = GetIdentifiers("action").ToList();
That will give you all the String Resources, which contain the string action.

MVCCrud Using LinqToEntities

There is a sample application called MVCCrud. This example is quite good and I would like to use it as the framework on a project that I am working on.
The problem is that MVCCrud uses LingToSQL and I would like to use LinqToEntities. I got most everything to work correctly once I converted over to LinqToEntities except one place.
In the following code on the lines i = typeof(TModel).GetProperty(primaryKey).GetValue(p, null),
cell = getCells(p)
it gives a Linq to Entities does not recognize GetValue.
Can someone help me refactor the following code?
items = items.OrderBy(string.Format("{0} {1}", sidx, sord)).Skip(pageIndex * pageSize).Take(pageSize).AsQueryable();
// Generate JSON
var jsonData =
new
{
total = totalPages,
page,
records = totalRecords,
rows = items.Select(
p => new
{
// id column from repository
i = typeof(TModel).GetProperty(primaryKey).GetValue(p, null),
cell = getCells(p)
}).ToArray()
};
return Json(jsonData);
and here is the getCell method:
private string[] getCells(TModel p)
{
List<string> result = new List<string>();
string a = actionCell(p);
if (a != null)
{
result.Add(a);
}
foreach (string column in data_rows.Select(r => r.value))
{
try
{
// hack for tblcategory.name
string[] parts = column.Split('.');
// Set first part
PropertyInfo c = typeof(TModel).GetProperty(parts[0]);
object tmp = c.GetValue(p, null);
// loop through if there is more than one depth to the . eg tblCategory.name
for (int j = 1; j < parts.Length; j++)
{
c = tmp.GetType().GetProperty(parts[j]);
tmp = c.GetValue(tmp, null);
}
if (tmp.GetType() == typeof(DateTime))
{
result.Add(((DateTime)tmp).ToString(dateTimeFormat));
}
else if (tmp.GetType() == typeof(float))
{
result.Add(((float)tmp).ToString(decimalFormat));
}
else if (tmp.GetType() == typeof(double))
{
result.Add(((double)tmp).ToString(decimalFormat));
}
else if (tmp.GetType() == typeof(decimal))
{
result.Add(((decimal)tmp).ToString(decimalFormat));
}
else
{
result.Add(tmp.ToString());
}
}
catch (Exception)
{
result.Add(string.Empty);
}
}
return result.ToArray();
}
Do this ToList() instead of AsQueryable():
items = items.OrderBy(string.Format("{0} {1}", sidx, sord)).Skip(pageIndex * pageSize).Take(pageSize).ToList();
You can't execute any external method "within" linq query.
And may you say that was working in Linq2Sql then you should know when you call any external method "Like ToString()" Linq2Sql will fetch all data from database then handle your query in the memory and that maybe a serious harming if you have a lot of records.
For more information look at this

Adding 90000 XElement to XDocument

I have a Dictionary<int, MyClass>
It contains 100,000 items
10,000 items value is populated whilst 90,000 are null.
I have this code:
var nullitems = MyInfoCollection.Where(x => x.Value == null).ToList();
nullitems.ForEach(x => LogMissedSequenceError(x.Key + 1));
private void LogMissedSequenceError(long SequenceNumber)
{
DateTime recordTime = DateTime.Now;
var errors = MyXDocument.Descendants("ERRORS").FirstOrDefault();
if (errors != null)
{
errors.Add(
new XElement("ERROR",
new XElement("DATETIME", DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss:fff")),
new XElement("DETAIL", "No information was read for expected sequence number " + SequenceNumber),
new XAttribute("TYPE", "MISSED"),
new XElement("PAGEID", SequenceNumber)
)
);
}
}
This seems to take about 2 minutes to complete. I can't seem to find where the bottleneck might be or if this timing sounds about right?
Can anyone see anything to why its taking so long?
If your MyInfoCollection is huge, I wouldn't call ToList() on it just so you can use the ForEach extension method. Calling ToList() is going to create and populate a huge list. I'd remove the ToList() call, and make the .ForEach into a for each statement, or write a .ForEach extension method for IEnumerable<T>.
Then profile it and see how long it takes. One other thing to do is remove the find and null check of the ERRORS element. If it's not there, then don't call the for each statement above. That way you null check it one time instead of 90,000 times.
Plus as Michael Stum pointed out, I'd define a string to hold the value DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss:fff"), then reference it or pass it in. Plus, you don't even use this call:
DateTime recordTime = DateTime.Now;
This is what I would most likely do.
private void BuildErrorNodes()
{
const string nodeFormat = #"<ERROR TYPE=""MISSED""><DATETIME>{0}</DATETIME><DETAIL>No information was read for expected sequence number {1}</DETAIL><PAGEID>{1}</PAGEID></ERROR>";
var sb = new StringBuilder("<ERRORS>");
foreach (var item in MyInfoCollection)
{
if (item.Value == null)
{
sb.AppendFormat(
nodeFormat,
DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss:fff"),
item.Key + 1
);
}
}
sb.Append("</ERRORS>");
var errorsNode = MyXDocument.Descendants("ERRORS").FirstOrDefault();
errorsNode.ReplaceWith(XElement.Parse(sb.ToString()));
}
How about replacing the method call with a LINQ query?
static void Main(string[] args)
{
var MyInfoCollection = (from key in Enumerable.Range(0, 100000)
let value = (MoreRandom() % 10 != 0)
? (string)null
: "H"
select new { Value = value, Key = key }
).ToDictionary(k => k.Key, v => v.Value);
var MyXDocument = new XElement("ROOT",
new XElement("ERRORS")
);
var sw = Stopwatch.StartNew();
//===
var errorTime = DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss:fff");
var addedIndex = MyInfoCollection.Select((item, index) =>
new
{
Value = item.Value,
Key = item.Key,
Index = index
});
var errorQuery = from item in addedIndex
where string.IsNullOrEmpty(item.Value)
let sequenceNumber = item.Key + 1
let detail = "No information was read for expected " +
"sequence number " + sequenceNumber
select new XElement("ERROR",
new XElement("DATETIME", errorTime),
new XElement("DETAIL", detail),
new XAttribute("TYPE", "MISSED"),
new XElement("PAGEID", sequenceNumber)
);
var errors = MyXDocument.Descendants("ERRORS").FirstOrDefault();
if (errors != null)
errors.Add(errorQuery);
//===
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds); //623
}
static RandomNumberGenerator rand = RandomNumberGenerator.Create();
static int MoreRandom()
{
var buff = new byte[1];
rand.GetBytes(buff);
return buff[0];
}

Categories