Using LINQ with Action to delete old files - c#

I would like to do something like
Action<FileInfo> deleter = f =>
{
if (....) // delete condition here
{
System.IO.File.Delete(f.FullName);
}
};
DirectoryInfo di = new DirectoryInfo(_path);
di.GetFiles("*.pdf").Select(deleter); // <= Does not compile!
di.GetFiles("*.txt").Select(deleter); // <= Does not compile!
di.GetFiles("*.dat").Select(deleter); // <= Does not compile!
in order to delete old files from a directory. But I do not know how to directly apply the delegate to the FilInfo[] without an explicit foreach (the idea listed above does not work of course).
Is it possible?

Select() is used to project items from TSource to TResult. In your case, you do not need Select because you're not projecting. Instead, use List<T>s ForEach method to delete files:
di.GetFiles("*.pdf").ToList().ForEach(deleter);

As DarkGray suggests you could, if somewhat unusually, utilise the Select to firstly action the file, and then return a null collection. I would recommend utilising the ForEach extension, like so:
ForEach LINQ Extension
public static void ForEach<TSource>(this IEnumerable<TSource> source, Action<T> action)
{
foreach(TSource item in source)
{
action(item);
}
}
You should then be able to execute the action on the array of FileInfo, as array is an enumerator. Like so:
Execution
Action<FileInfo> deleter = f =>
{
if (....) // delete condition here
{
System.IO.File.Delete(f.FullName);
}
};
DirectoryInfo di = new DirectoryInfo(_path);
di.GetFiles("*.pdf").ForEach(deleter);
Edit by Richard.
I do want to raise attention to the argument of foreach vs ForEach. In my opinion the ForEach statement should directly effect the object being passed in, and in this case it does. So I've contradicted myself. Oops! :)

di.GetFiles("*.pdf").Select(_=>{deleter(_);return null;});
or
di.GetFiles("*.pdf").ForEach(action);
public static class Hlp
{
static public void ForEach<T>(this IEnumerable<T> items, Action<T> action)
{
foreach (var item in items)
action(item);
}
}

Related

Can I evaluate two groups of items with a foreach loop in C# winforms?

I have a winforms TabControl and I am trying to cycle through all the controls contained in each tab. Is there a way to add and in a foreach loop or isn't it possible to evaluate more than one group of items? For example this is what I'd like to do:
foreach (Control c in tb_Invoices.Controls and tb_Statements.Controls)
{
//do something
}
OR
foreach (Control c in tb_Invoices.Controls, tb_Statements.Controls)
{
//do something
}
Is this possible, and if not, what is the next best thing? Do I need to use a for loop?
foreach(TabPage page in yourTabControl.TabPages){
foreach(Control c in page.Controls){
LoopThroughControls(c);
}
}
private void LoopThroughControls(Control parent){
foreach(Control c in parent.Controls)
LoopThroughControls(c);
}
Final solution:
var allControls = from TabPage p in tabControl.TabPages
from Control c in p.Controls
select c;
Original answer - use Concat:
var allControls = tb_Invoices.Controls.Cast<Control>()
.Concat(tb_Statements.Controls.Cast<Control>();
BTW I think it's better to use simple non-generic ArrayList here
ArrayList allControls = new ArrayList();
allControls.AddRange(tb_Invoices.Controls);
allControls.AddRange(tb_Statements.Controls);
What I like to do is:
var list = new List<T>();
list.AddRange(list1);
list.AddRange(list2);
list.AddRange(list3);
list.AddRange(list4);
foreach (T item in list)
{
.....
}
You can do using one foreach loop by writing it recursively. This will ensure to loop through all the controls of all types in your form.
private void LoopAllControls(Control YourObject)
foreach(Control c in YourObject.Controls)
{
if(C.Controls.Count > 0)
LoopAllControls(c.Controls);
//your code
}
You could do:
public static void ForAllChildren(Action<Control> action,
params Control[] parents)
{
foreach(var p in parents)
foreach(Control c in p.Controls)
action(c);
}
Called like:
ForAllChildren(x => Foo(x), tb_Invoices, tb_Statements);
You might be hit a little on performance for the action invocation though in which case you could just use a nested foreach:
foreach (var p in new Control[] { tb_Invoices, tb_Statements })
foreach (Control c in p.Controls)
Foo(c);
Similarly, a generic solution to loop through all items in any non-generic IEnumerable might be (although a bit like using a sledgehammer to drive a nail):
public static void ForEachAll<T>(Action<T> action,
params System.Collections.IEnumerable[] collections)
{
foreach(var collection in collections)
foreach(var item in collection.Cast<T>())
action(item);
}
Called like:
ForEachAll<Control>(x => Foo(x), tb_Invoices.Controls, tb_Statements.Controls);
If you are not in a position to use LINQ (like stuck with .NET2), I suggest you use this method:
public static IEnumerable<T> Concat<T>(params IEnumerable<T>[] args)
{
foreach (IEnumerable<T> collection in args)
{
foreach (T item in collection)
{
yield return item;
}
}
}
Now you have a generic function that you can use with anything that's enumerable. Your loop can look like this:
foreach (Control c in Concat(tb_Invoices.Controls, tb_Statements.Controls))
{
//do something
}
Simple, cheap and expressive!
EDIT: if your collection do not implement IEnumerable<T> but only IEnumerable, you can add an overload that will accept the latter. Everything stays the same, except that T changes to object in the nested loop.

C# List<t>.ForEach(delegate(type t) and how to remove items as the list is iterated through

This may be considered bad programming, but prior to .net 4, I used to heavily use code similar to this:
enemyList.ForEach(delegate(Enemy e)
{
e.Update();
if (someCondition)
enemyList.Remove(e);
});
Now, I'm going through an updating some old projects, and there are a LOT of code thats going to have to be changed since ForEach was removed.. Now, I do have an extension to allow me to use the ForEach :
public static void ForEach<T>(this IEnumerable<T> sequence, Action<T> action)
{
if (sequence == null) throw new ArgumentNullException("sequence");
if (action == null) throw new ArgumentNullException("action");
foreach (T item in sequence)
action(item);
}
I know I can do this:
var index = 0;
while(index < enemyList.Count)
{
if(condition)
enemyList.RemoveAt(index);
else
index++;
}
But some of those would be a pain to rewrite like that.. Is there any way to add that functionality back so that I can iterate through that list, remove the items I need without having to go back and rewrite and edit all of those functions ? I still consider myself a newbie to coding, and I just can't figure this one out.. Any help would be appreciated!
********* EDIT *********
So I guess it boils down to rewriting a lot of code.. I have a lot of code such as this that I just pulled out of a project:
GameplayScreen.gameWorld.shipList.ForEach(delegate(Ship s)
{
if (eS.originalShipsID == s.shipID)
{
if (!eS.Alive || eS.health <= 0)
{
// this one sunk...
string log = "0" + s.shipName + " was sunk in battle.. All crew and cargo were lost.";
AddLogEntry(log);
totalCrewLost += s.currentCrew;
GameplayScreen.gameWorld.shipList.Remove(s);
}
}
});
I was just hoping there was a way to not have to rewrite all of that.. So time to update and change the way I code apparently. Thanks!
Use the list's RemoveAll method.
You can refactor the code to:
enemyList.RemoveAll(enemy => enemy.SomeCondition);
Not only is it better than the while loop, I'd argue it's quite a bit better than the Foreach method.
You can't. The only way would be to add the items to remove to another list and then iterate over that list and remove them after the initial iteration.
A better option would be to use a reverse for loop to iterate over the values. You can then safely remove the items during the initial iteration:
for (var i = enemyList.Count() - 1; i >= 0; i--) {
{
if(condition) enemyList.RemoveAt(i);
}
Since you said you do it a lot, why not do something like this:
public static void RemoveIfTrue<T>(this ICollection<T> list, Func<T, bool> condition)
{
List<T> itemsToRemove = list.Where(condition).ToList();
foreach (var item in itemsToRemove)
{
list.Remove(item);
}
}
Then you could use it like:
myList.RemoveIfTrue(x => YourConditionIsTrue)
That way you don't have a bunch of duplication of logic.
If you're using a List<T>, you can use List<T>.RemoveAll(Predicate<T> match)
So there is a built-in thing to do this already.
Even better - the built-in one knows exactly how to avoid problems modifying the collection while iterating over it. And because it has access to the private internals, it's more efficient too.
So, just using the List class itself you can write code like this:
enemies.RemoveAll(enemy => (enemy.Health <= 0));
This is possible with a small tweak. Here's an example:
public static class Extensions
{
public static void ForEach<T>(this IList<T> list, Action<T> action)
{
for (int i = 0; i < list.Count; i++)
{
action(list[i]);
}
}
}
class Program
{
static void Main(string[] args)
{
List<string> vals = new List<string>(new string[] { "a", "bc", "de", "f", "gh", "i", "jk" });
vals.ToList().ForEach<string>(delegate(string value)
{
if (value.Length > 1)
{
vals.Remove(value);
}
});
vals.ToList().ForEach<string>(delegate(string value)
{
Console.WriteLine(value);
});
Console.ReadKey();
}
}
Now, there are a couple of things worth mentioning here: first, normally elements would be skipped. However, a separate copy of the list is made by calling ToList(). Second, you should be careful to do this only with reference types - i.e. not with primitive types - otherwise you'll remove more than a single element with the remove method.
EDIT
I'd also like to add that probably any of the posted alternatives are better - but I thought it was interesting that this could be done; it's less performant but probably quicker to chuck into existing code.

Problems with ForEach extension method over IEnumerable<Request.Files>

While doing some basic validation on the ASP.Net (4.0) Request.Files (upload) collection I decided to try it with LINQ.
The collection is IEnumerable<T> and so doesn't offer ForEach. Foolishly I decided to build an extension method that would do the job. Sorry to say not so much success...
Running the extension method (below) raises next error:
Unable to cast object of type 'System.String' to type 'System.Web.HttpPostedFile'
There is clearly something I am not getting, but I can't see what it is, so at the risk of looking like an idiot (wont be the first time) here is the code in 3 chunks, along with a promise of gratitude for any help.
First, the extension method with an Action parameter:
//Extend ForEach to IEnumerated Files
public static IEnumerable<HttpPostedFileWrapper> ForEach<T>(this IEnumerable<HttpPostedFileWrapper> source, Action<HttpPostedFileWrapper> action)
{
//breaks on first 'item' init
foreach (HttpPostedFileWrapper item in source)
action(item);
return source;
}
The error occurs when the internal foreach loop hits the 'item' in 'source'.
Here is the calling code (variables MaxFileTries and attachPath are properly set previously) :
var files = Request.Files.Cast<HttpPostedFile>()
.Select(file => new HttpPostedFileWrapper(file))
.Where(file => file.ContentLength > 0
&& file.ContentLength <= MaxFileSize
&& file.FileName.Length > 0)
.ForEach<HttpPostedFileWrapper>(f => f.SaveUpload(attachPath, MaxFileTries));
And lastly, the Action target, Saving the upload file - we don't appear to ever even get to here, but just in case, here it is:
public static HttpPostedFileWrapper SaveUpload(this HttpPostedFileWrapper f, string attachPath, int MaxFileTries)
{
// we can only upload the same file MaxTries times in one session
int tries = 0;
string saveName = f.FileName.Substring(f.FileName.LastIndexOf("\\") + 1); //strip any local
string path = attachPath + saveName;
while (File.Exists(path) && tries <= MaxFileTries)
{
tries++;
path = attachPath + " (" + tries.ToString() + ")" + saveName;
}
if (tries <= MaxFileTries)
{
if (!Directory.Exists(attachPath)) Directory.CreateDirectory(attachPath);
f.SaveAs(path);
}
return f;
}
I confess that some of the above is a cobbling together of "bits found", so I am likely getting what I deserve, but if anyone has a good understanding of (or has at least been through) this, maybe I can learn something.
Thanks for any.
Why don't you just call ToList().ForEach() on the original IEnumerable<T>.
I think this is what you want
Your class that extends HttpFileCollection should be
public static class HttpPostedFileExtension
{
//Extend ForEach to IEnumerated Files
public static void ProcessPostedFiles(this HttpFileCollection source, Func<HttpPostedFile, bool> predicate, Action<HttpPostedFile> action)
{
foreach (var item in source.AllKeys)
{
var httpPostedFile = source[item];
if (predicate(httpPostedFile))
action(httpPostedFile);
}
}
}
And then you can use it like so:
Request.Files.ProcessPostedFiles(
postedFile =>
{
return (postedFile.ContentLength > 0 && postedFile.FileName.Length > 0);
},
pFile =>
{
//Do something with pFile which is an Instance of HttpPosteFile
});
First, write a common extension method:
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
foreach (T item in source)
action(item);
return source; // or void, without return
}
Then, Request.Files is castable to IEnumerable<string>, not IEnumerable<HttpPostedFile>:
IEnumerable<string> requestFiles = this.Request.Files.Cast<string>();
but indeed System.Web.UI.WebControls.FileUpload.PostedFile is HttpPostedFile:
HttpPostedFile file = fileUpload.PostedFile;
HttpPostedFileWrapper wrapper = new HttpPostedFileWrapper(file);
but it's single, not a collection. Where from did you get your collection?
Another extension method:
public static IEnumerable<HttpPostedFile> ToEnumerable(this HttpFileCollection collection)
{
foreach (var item in collection.AllKeys)
{
yield return collection[item];
}
}
Usage:
IEnumerable<HttpPostedFile> files = this.Request.Files.ToEnumerable();
IEnumerable<HttpPostedFileWrapper> wrappers = files.Select(f => new HttpPostedFileWrapper(f));

Elegantly refactoring code like this (to avoid a flag)

I have a function running over an enumerable, but the function should be a little bit different for the first item, for example:
void start() {
List<string> a = ...
a.ForEach(DoWork);
}
bool isFirst = true;
private void DoWork(string s) {
// do something
if(isFirst)
isFirst = false;
else
print("first stuff");
// do something
}
How would you refactor this to avoid that ugly flag?
Expounding on Jimmy Hoffa's answer if you actually want to do something with the first item you could do this.
DoFirstWork(a[0])
a.Skip(1).ForEach(DoWork)
If the point is that it is separate in logic from the rest of the list then you should use a separate function.
It might be a bit heavy handed, but I pulled this from another SO question a while back.
public static void IterateWithSpecialFirst<T>(this IEnumerable<T> source,
Action<T> firstAction,
Action<T> subsequentActions)
{
using (IEnumerator<T> iterator = source.GetEnumerator())
{
if (iterator.MoveNext())
{
firstAction(iterator.Current);
}
while (iterator.MoveNext())
{
subsequentActions(iterator.Current);
}
}
}
Check out Jon Skeet's smart enumerations.
They are part of his Miscellaneous Utility Library
EDIT: added usage example, added a ForFirst method, reordered my paragraphs.
Below is a complete solution.
Usage is either of the following:
list.ForFirst(DoWorkForFirst).ForRemainder(DoWork);
// or
list.ForNext(1, DoWorkForFirst).ForRemainder(DoWork);
The crux is the ForNext method, which performs an action for the specified next set of items from the collection and returns the remaining items. I've also implemented a ForFirst method that simply calls ForNext with count: 1.
class Program
{
static void Main(string[] args)
{
List<string> list = new List<string>();
// ...
list.ForNext(1, DoWorkForFirst).ForRemainder(DoWork);
}
static void DoWorkForFirst(string s)
{
// do work for first item
}
static void DoWork(string s)
{
// do work for remaining items
}
}
public static class EnumerableExtensions
{
public static IEnumerable<T> ForFirst<T>(this IEnumerable<T> enumerable, Action<T> action)
{
return enumerable.ForNext(1, action);
}
public static IEnumerable<T> ForNext<T>(this IEnumerable<T> enumerable, int count, Action<T> action)
{
if (enumerable == null)
throw new ArgumentNullException("enumerable");
using (var enumerator = enumerable.GetEnumerator())
{
// perform the action for the first <count> items of the collection
while (count > 0)
{
if (!enumerator.MoveNext())
throw new ArgumentOutOfRangeException(string.Format(System.Globalization.CultureInfo.InvariantCulture, "Unexpected end of collection reached. Expected {0} more items in the collection.", count));
action(enumerator.Current);
count--;
}
// return the remainder of the collection via an iterator
while (enumerator.MoveNext())
{
yield return enumerator.Current;
}
}
}
public static void ForRemainder<T>(this IEnumerable<T> enumerable, Action<T> action)
{
if (enumerable == null)
throw new ArgumentNullException("enumerable");
foreach (var item in enumerable)
{
action(item);
}
}
}
I felt a bit ridiculous making the ForRemainder method; I could swear that I was re-implementing a built-in function with that, but it wasn't coming to mind and I couldn't find an equivalent after glancing around a bit. UPDATE: After reading the other answers, I see there apparently isn't an equivalent built into Linq. I don't feel so bad now.
using System.Linq; // reference to System.Core.dll
List<string> list = ..
list.Skip(1).ForEach(DoWork) // if you use List<T>.ForEeach()
but I recommend you to write your one:
public static void ForEach(this IEnumerable<T> collection, Action<T> action)
{
foreach(T item in collection)
action(item);
}
So you could do just next:
list.Skip(1).ForEach(DoWork)
It's hard to say what the "best" way to handle the first element differently is without knowing why it needs to be handled differently.
If you're feeding the elements of the sequence into the framework's ForEach method, you can't elegantly provide the Action delegate the information necessary for it to determine the element parameter's position in the source sequence, so I think an extra step is necessary. If you don't need to do anything with the sequence after you loop through it, you could always use a Queue (or Stack), pass the first element to whatever handler you're using through a Dequeue() (or Pop()) method call, and then you have the leftover "homogeneous" sequence.
It might seem rudimentary with all the shiny Linq stuff available, but there's always the old fashion for loop.
var yourList = new List<int>{1,1,2,3,5,8,13,21};
for(int i = 0; i < yourList.Count; i++)
{
if(i == 0)
DoFirstElementStuff(yourList[i]);
else
DoNonFirstElementStuff(yourList[i]);
}
This would be fine if you don't want to alter yourList inside the loop. Else, you'll probably need to use the iterator explicitly. At that point, you have to wonder if that's really worth it just to get rid of an IsFirst flag.
Depends on how you're "handling it differently". If you need to do something completely different, then I'd recommend handling the first element outside the loop. If you need to do something in addition to the regular element processing, then consider having a check for the result of the additional processing. It's probably easier to understand in code, so here's some:
string randomState = null; // My alma mater!
foreach(var ele in someEnumerable) {
if(randomState == null) randomState = setState(ele);
// handle additional processing here.
}
This way, your "flag" is really an external variable you (presumably) need anyway, so you're not creating a dedicated variable. You can also wrap it in an if/else if you don't want to process the first element like the rest of the enumeration.

How to Remove unneccessary list using lambda and functional C# paradigm

Hello Functional C# Friends,
So this time i am trying to compact my code and write in more functional , lambda style, as well as i would like to avaoid creating unneccessary lists and classes and let compiler do the work for me. I did manage to convert some small piece of code in functional way but after that i dont have much idea as how to go about.
var errorList = new List<DataRow>();
IEnumerable<DataRow> resultRows = GetResultRows();
resultRows
.Filter(row => row.Field<string>("status").Equals("FAILURE", StringComparison.InvariantCultureIgnoreCase))
.ForEach(row => { errorList.Add(row); });
if (errorList.Count > 0)
{
var excludedBooks = new List<string>();
foreach (DataRow row in errorList)
{
if (ToUseBooksList.Contains((string)row["main_book"]))
{
BookCheckResults.AddRow(string.Format("Error for MainBook {0}, RiskType {1}",
row["main_book"], row["risk_type"]));
if (!excludedBooks.Contains((string)row["main_book"]))
{
excludedBooks.Add((string)row["main_book"]);
}
}
}
}
My Extension methods :
public static void ForEach<T>(this IEnumerable<T> collection, Action<T> action)
{
if (collection == null)
throw new ArgumentNullException("collection");
if (action == null)
throw new ArgumentNullException("action");
foreach (var item in collection)
action(item);
}
public static IEnumerable<T> Filter<T>(this IEnumerable<T> source, Predicate<T> func)
{
foreach (var item in source)
{
if (func(item))
yield return item;
}
}
I will highly appreciate if you can help me structing this code is more functional, lambda style.
Why on earth did you write you own Filter extension method when Where is available?
The ForEach extension method usually isn't worth the bother.
Eric Lippert has blogged about it, and his philosophical objection to it is that it looks like a side-effect free expression (like most Linq features) but it is actually a side-effecting imperative statement in disguise.
If you want to carry out an action for each item on a list, use the foreach statement. That's what it's for.
If you want to manipulate lists of actions, then you can do that, but then you want IEnumerable<Action>.
For the first part of your code, how about:
var errorList = GetResultRows().Where(row => row.Field<string>("status").Equals("FAILURE", StringComparison.InvariantCultureIgnoreCase)
.ToList();
You have a List<string> called excluded books. Use HashSet<string> instead, and you don't need to check if a string is already added to it:
var excludedBooks = new HashSet<string>();
foreach (DataRow row in errorList)
{
if (ToUseBooksList.Contains((string)row["main_book"]))
{
BookCheckResults.AddRow(string.Format("Error for MainBook {0}, RiskType {1}",
row["main_book"], row["risk_type"]));
excludedBooks.Add((string)row["main_book"]);
}
}
You can also filter the list with Where:
var excludedBooks = new HashSet<string>();
foreach (DataRow row in errorList.Where(r => ToUseBooksList.Contains((string)r["main_book"]))
{
BookCheckResults.AddRow(string.Format("Error for MainBook {0}, RiskType {1}",
row["main_book"], row["risk_type"]));
excludedBooks.Add((string)row["main_book"]);
}

Categories

Resources