I was wondering if it's possible to turn this while loop into a lambda statement? I know it's possible if it were a for or a foreach loop, but it's a normal, plain while loop:
while (path.Substring(path.Length - 4) != ".txt" || path.Substring(path.Length - 4) != ".xml")
{
Console.WriteLine("File not a .txt or .xml extension! Enter the file name:");
path = Console.ReadLine();
}
If it is possible, how would one transform this loop into such a lambda statement?
Given that the comments on the question suggest that this isn't really about lambdas, but about minimizing code, here are some small suggestions to avoid some code duplication:
string[] validExtensions = { ".txt", ".xml" };
do
{
Console.WriteLine("Enter the file name:");
path = Console.ReadLine();
if (!validExtensions.Contains(Path.GetExtension(path)))
{
Console.Write("File not a .txt or .xml extension! ");
path = null;
}
}
while (path == null);
Checking for another extension merely requires adding the extension to the array, it doesn't require duplicating the code to determine the extension. The string "Enter the file name:" only has to appear once, even if you want a slightly different message for the first prompt. The code to read a line also only has to appear once.
Personally, I'd say the duplication you had is so small that there is no need to avoid it yet, but you may find this useful if, for example, you need to allow three more extensions, or read from some other location where a single function call does not suffice.
Some additional comments:
Console.ReadLine() can return null. Just like the code in your question, this version doesn't handle that properly.
Case in file extensions is usually ignored. Do you really want to reject ".TXT" as a file extension?
Your while condition path.Substring(path.Length - 4) != ".txt" || path.Substring(path.Length - 4) != ".xml" would never be false. It could be true, or it could throw an exception, but the loop would never terminate normally.
In case you want to do this for learning and academic purposes:
Func<string[], string> getFile = (validExtensions) =>
{
string path = "";
while (!validExtensions.Contains(Path.GetExtension(path)))
{
Console.WriteLine("File not a .txt or .xml extension! Enter the file name:");
path = Console.ReadLine();
}
return path;
};
string path = getFile.Invoke(new string[]{ ".txt", ".xml" });
Use .Invoke(string[]) to call it, passing in desired file extensions.
To pass it into a method, for example:
public string UseFunc(Func<string[], string> getFile, string[] validExtensions)
{
return getFile.Invoke(validExtensions);
}
string path = foo.UseFunc(getFile);
Related
Can someone tell me an efficient way to check if a directory and its contents have changed using C#? The program is not running all the time so FileWatcher is not an option. Also, this test must be done quickly and need not be perfect, just pretty good. By this I mean I'd be happy if I could quickly check that for a given directory, the same subdirectories were still present. And the same file names with the same file sizes. I guess it might also be nice to verify the "modified date". It's not necessary or desirable to open each file and verify contents. That would take too long. I can easily save the info (file sizes, etc.) to a file and read it the next time I run the program.
The background for this is that I'm developing a search program to search our proprietary files. I save the results to a JSON file and the next time the search is done, I simply read from JSON. Of course ONLY if the directory (or subdirectory) has not changed.
I've posted the code below that I currently use for checking. It's way too slow. The biggest time hog is the checking to see if all the directories match, but even the first line ("Directory.Exists") takes some time.
Please let me know if I can provide more details or clarification.
Thanks,
Dave
if (Directory.Exists(directoryCache.DirectoryName) == false)
return false;
// look at sessZip
ulong fileSize = 0;
string[] sessZipEntries = Directory.GetFiles(directoryCache.DirectoryName,
"*.sessZip", SearchOption.TopDirectoryOnly);
foreach (string fileName in sessZipEntries)
{
FileInfo fileInfo = new FileInfo(fileName);
fileSize += (ulong)fileInfo.Length;
}
if (fileSize != directoryCache.SessZipSize)
return false;
string[] directories = Directory.GetDirectories(directoryCache.DirectoryName);
foreach (string directory in directories)
{
if (directoryCache.SubDirectoriesList.FirstOrDefault(x => x.DirectoryName == directory) == null)
{
return false;
}
}
foreach (DirectoryCacheItem subDir in directoryCache.SubDirectoriesList)
{
if (directories.Contains(subDir.DirectoryName) == false)
{
return false;
}
}
return true;
You will likely need to hash the contents of the folder and store that off. Then just compare it. MD5 should be sufficient.
If you want easier you can follow the MS example, but it isn't foolproof.
https://msdn.microsoft.com/en-us/library/mt693036.aspx
Based on Tim's answer (which I'm marking as answer), I was able to come up with a pretty good, fast solution for checking that the directory has not changed. Thanks Tim for the idea and link. I have:
public bool VerifyCacheIntegrity()
{
if (_directoryCache == null || string.IsNullOrEmpty(_directoryCache.DirectoryName))
return false;
System.IO.DirectoryInfo dir1 = new System.IO.DirectoryInfo(_directoryCache.DirectoryName);
FileInfo[] list1 = dir1.GetFiles("*.sessZip", System.IO.SearchOption.AllDirectories);
FileCompare myFileCompare = new FileCompare();
return list1.SequenceEqual(_directoryCache.FileInfoLst, myFileCompare);
}
FileCompare is defined as:
internal class FileCompare : System.Collections.Generic.IEqualityComparer<System.IO.FileInfo>
{
public FileCompare() { }
public bool Equals(System.IO.FileInfo f1, System.IO.FileInfo f2)
{
return (f1.Name == f2.Name &&
f1.Length == f2.Length);
}
// Return a hash that reflects the comparison criteria. According to the
// rules for IEqualityComparer<T>, if Equals is true, then the hash codes must
// also be equal. Because equality as defined here is a simple value equality, not
// reference identity, it is possible that two or more objects will produce the same
// hash code.
public int GetHashCode(System.IO.FileInfo fi)
{
string s = String.Format("{0}{1}", fi.Name, fi.Length);
return s.GetHashCode();
}
}
Obviously, the FileCompare could be expanded to include "Modified Date" comparisons or anything else. Finally, for completeness, I show how FileInfoLst is populated. This is the list that ultimately goes into a file and provides a snapshot of what the directory structure looked like.
public bool BuildTree(string directory, string searchItem = null, List<string> matches = null, string searchField = null)
{
if (!Directory.Exists(directory))
return false;
_directoryCache = BuildDirectoryCache(directory, searchItem, matches, searchField);
System.IO.DirectoryInfo dir1 = new System.IO.DirectoryInfo(directory);
FileInfo[] list1 = dir1.GetFiles("*.sessZip", System.IO.SearchOption.AllDirectories);
_directoryCache.FileInfoLst = list1;
return true;
}
I have a (hopefully) simple C# question.
I am parsing arguments in a program where a file will be read from command line, I've allowed for both short and long arguments as input (so for my scenario /f and file are both valid)
The value after either of the above arguments should be the file name to be read.
What I want to do is find this file name in the array based off whichever argument is chosen and copy it in to a string while not leaving any loopholes.
I have functioning code, but I'm not really sure it's "efficient" (and secure).
Code (comments and writes removed):
if ( args.Contains("/f") || args.Contains("file"))
{
int pos = Array.IndexOf(args, "/f");
if (pos == -1)
pos = Array.IndexOf(args, "file");
if (pos > -1)
pos++;
inputFile = (args[pos]);
if (File.Exists(inputFile) == false)
{
Environment.Exit(0);
}
}
Is there a more efficient way to do this, perhaps using some nifty logic in the initial if statement to check which parameter is valid and then do a single check on that parameter?
Using 4 ifs and 2 Array.IndexOf's seems horrible just to support 2 different ways to allow someone to say they want to input a file...
Thanks! And I'm sorry if this seems trivial or is not what SO is meant for. I just don't have any real way to get feedback on my coding practises unfortunately.
Your solution won't scale well. Imagine you have two different arguments with a short and long form. How many conditionals and index checks would that be?
You'd be better off using an existing tool (e.g. Command Line Parser Library) for argument parsing.
One problem I see with the code you provided is that, it will fail if the /f or file is the last argument.
If you don't want to write or use complete argument parsing code, the following code will work slightly better.
var fileArguments = new string[] { "/f", "file" };
int fileArgIndex = Array.FindIndex(args,
arg => fileArguments.Contains(arg.ToLowerInvariant()));
if (fileArgIndex != -1 && fileArgIndex < args.Length - 1)
{
inputFile = args[fileArgIndex + 1];
if (!File.Exists(inputFile))
{
Environment.Exit(0);
}
}
You could write a simple argument parser for your specific need and still have support for "new" scenarios. For example, in your entry method have
// The main entry point for the application.
[STAThread]
static void Main(string[] args)
{
// Parse input args
var parser = new InputArgumentsParser();
parser.Parse(args);
....
}
Where your InputArgumentsParser could be something similar to
public class InputArgumentsParser
{
private const char ArgSeparator = ':';
private Dictionary<string[],Action<string>> ArgAction =
new Dictionary<string[],Action<string>>();
public InputArgumentsParser()
{
// Supported actions to take, based on args
ArgAction.Add(new[] { "/f", "/file" }, (param) =>
Console.WriteLine(#"Received file argument '{0}'", param));
}
/// Parse collection, expected format is "<key>:<value>"
public void Parse(ICollection<string> args)
{
if (args == null || !args.Any())
return;
// Iterate over arguments, extract key/value pairs
foreach (string arg in args)
{
string[] parts = arg.Split(ArgSeparator);
if (parts.Length != 2)
continue;
// Find related action and execute if found
var action = ArgAction.Keys.Where(key =>
key.Contains(parts[0].ToLowerInvariant()))
.Select(key => ArgAction[key]).SingleOrDefault();
if (action != null)
action.Invoke(parts[1]);
else
Console.WriteLine(#"No action for argument '{0}'", arg);
}
}
}
In this case /f:myfile.txt or /file:myfile.txt would spit out to console
Received file argument 'myfile.txt'
This sort of ties back to a question I had earlier about a regex to search for a method containing a particular string, and someone suggested I use this MS tool called Roslyn but it's not available for VS2010 since 2012 came out.
So I'm writing this small utility to keep a list of every file in my solution that contains a particular method declaration (something like 3k of the 25k files overload this method). Then I simply want to filter that list of files to only ones that contain += inside the body of the method.
static void DirSearch(string dir)
{
string[] files = Directory.GetFiles(dir, "*.*", SearchOption.AllDirectories);
foreach (var file in files)
{
var contents = File.ReadAllText(file);
if (contents.Contains("void DetachEvents()"))
{
//IF DetachEvents CONTAINS += THEN...
WriteToFile(file);
}
}
}
This method iterates over all the folders and writes the file name to a text file if it contains the key method, but I have no idea how to extract just whatevers in the method body, since it's overloaded all 3K instances of the method are different.
Would the best approach to be get the index of the method name, then the index of each { and } until I encounter the next accessor modifier (signifying I've gotten to the end of DetachEvents)? Then I could just search between indexOfMethod and indexOfEndMethod for +=.
But it sounds really sloppy, I was hoping someone might have a better idea?
Do you have to do this in code? Is this a one time utility to identify the problem methods? Why not use something like Notepad++ and it's Find in Files capabilities. You can filter your find pretty easily and even apply regex (I think). From there you can copy the results which include the file name (i.e. someclassfile.cs) and get a list from there.
I wrote this really sloppy winform that lets the user type in the folder to the code base, the method name, and the flagrant text they're looking for. Then it loops over every file in the directory and calls this method on a string that contains all the text of the file. It returns true if the user-entered flagrant data is present, then the method that calls this adds the file its on to a list. Anyways, here's the major code:
private bool ContainsFlag(string contents)
{
int indexOfMethodDec = contents.IndexOf(_method);
int indexOfNextPublicMethod = contents.IndexOf("public", indexOfMethodDec);
if (indexOfNextPublicMethod == -1)
indexOfNextPublicMethod = int.MaxValue;
int indexOfNextPrivateMethod = contents.IndexOf("private", indexOfMethodDec);
if (indexOfNextPrivateMethod == -1)
indexOfNextPrivateMethod = int.MaxValue;
int indexOfNextProtectedMethod = contents.IndexOf("protected", indexOfMethodDec);
if (indexOfNextProtectedMethod == -1)
indexOfNextProtectedMethod = int.MaxValue;
int[] indeces = new int[3]{indexOfNextPrivateMethod,
indexOfNextProtectedMethod,
indexOfNextPublicMethod};
int closestToMethod = indeces.Min();
if (closestToMethod.Equals(Int32.MaxValue))
return false; //This should probably do something different.. This condition is true if the method you're reading is the last method in the class, basically
if (closestToMethod - indexOfMethodDec < 0)
return false;
string methodBody = contents.Substring(indexOfMethodDec, closestToMethod - indexOfMethodDec);
if (methodBody.Contains(_flag))
return true;
return false;
}
Plenty of room for improvement, this is mostly just a proof-of-concept thing that'll get used maybe twice per year internally. But for my purposes it worked. Should be a good starting-point for something more sophisticated if anyone needs it.
I have a text file, which I am trying to insert a line of code into. Using my linked-lists I believe I can avoid having to take all the data out, sort it, and then make it into a new text file.
What I did was come up with the code below. I set my bools, but still it is not working. I went through debugger and what it seems to be going on is that it is going through the entire list (which is about 10,000 lines) and it is not finding anything to be true, so it does not insert my code.
Why or what is wrong with this code?
List<string> lines = new List<string>(File.ReadAllLines("Students.txt"));
using (StreamReader inFile = new StreamReader("Students.txt", true))
{
string newLastName = "'Constant";
string newRecord = "(LIST (LIST 'Constant 'Malachi 'D ) '1234567890 'mdcant#mail.usi.edu 4.000000 )";
string line;
string lastName;
bool insertionPointFound = false;
for (int i = 0; i < lines.Count && !insertionPointFound; i++)
{
line = lines[i];
if (line.StartsWith("(LIST (LIST "))
{
values = line.Split(" ".ToCharArray());
lastName = values[2];
if (newLastName.CompareTo(lastName) < 0)
{
lines.Insert(i, newRecord);
insertionPointFound = true;
}
}
}
if (!insertionPointFound)
{
lines.Add(newRecord);
}
You're just reading the file into memory and not committing it anywhere.
I'm afraid that you're going to have to load and completely re-write the entire file. Files support appending, but they don't support insertions.
you can write to a file the same way that you read from it
string[] lines;
/// instanciate and build `lines`
File.WriteAllLines("path", lines);
WriteAllLines also takes an IEnumerable, so you can past a List of string into there if you want.
one more issue: it appears as though you're reading your file twice. one with ReadAllLines and another with your StreamReader.
There are at least four possible errors.
The opening of the streamreader is not required, you have already read
all the lines. (Well not really an error, but...)
The check for StartsWith can be fooled if you lines starts with blank
space and you will miss the insertionPoint. (Adding a Trim will remove any problem here)
In the CompareTo line you check for < 0 but you should check for == 0. CompareTo returns 0 if the strings are equivalent, however.....
To check if two string are equals you should avoid using CompareTo as
explained in MSDN link above but use string.Equals
List<string> lines = new List<string>(File.ReadAllLines("Students.txt"));
string newLastName = "'Constant";
string newRecord = "(LIST (LIST 'Constant 'Malachi 'D ) '1234567890 'mdcant#mail.usi.edu 4.000000 )";
string line;
string lastName;
bool insertionPointFound = false;
for (int i = 0; i < lines.Count && !insertionPointFound; i++)
{
line = lines[i].Trim();
if (line.StartsWith("(LIST (LIST "))
{
values = line.Split(" ".ToCharArray());
lastName = values[2];
if (newLastName.Equals(lastName))
{
lines.Insert(i, newRecord);
insertionPointFound = true;
}
}
}
if (!insertionPointFound)
lines.Add(newRecord);
I don't list as an error the missing write back to the file. Hope that you have just omitted that part of the code. Otherwise it is a very simple problem.
(However I think that the way in which CompareTo is used is probably the main reason of your problem)
EDIT Looking at your comment below it seems that the answer from Sam I Am is the right one for you. Of course you need to write back the modified array of lines. All the changes are made to an in memory array of lines and nothing is written back to a file if you don't have code that writes a file. However you don't need new file
File.WriteAllLines("Students.txt", lines);
What my program does is basically it lists file names (including it's extension) from a directory into a listbox. It then has a sorting function which sorts the list strings into alphabetical order.
Lastly it has a binary search function that allows the users to input any string which the program will then compare and display the matched results into a listbox.
Now, all these functions work perfectly however I can't seem to remove the extension off of a file name after a search.
For example in the scanning and sorting part it lists the file names as: filename.mp3
Now, what I want it do when the searching button is clicked is to remove the file extension and display just the filename.
private void buttonSearch_Click(object sender, RoutedEventArgs e)
{
listBox1.Items.Clear();
string searchString = textBoxSearchPath.Text;
int index = BinarySearch(list1, 0, list1.Count, searchString);
for (int n = index; n < list1.Count; n++)
{
//Removes file extension from last decimal point ''not working''
int i = list1[n].LastIndexOf(".");
if (i > 0)
list1[n].Substring(0, i);
// Adds items to list
if (list1[n].IndexOf(searchString, StringComparison.OrdinalIgnoreCase) != 0) break;
listBox1.Items.Add(list1[n]);
}
MessageBox.Show("Done");
}
C# is so easy that if something takes more than 2 minutes, there probably is a method for it in the Framework.
The Substring method returns a new fresh copy of the string, copied from the source one. If you want to "cut the extension off", then you must fetch what Substring returns and store it somewhere, i.e.:
int i = list1[n].LastIndexOf(".");
if (i > 0)
list1[n] = list1[n].Substring(0, i);
However, this is quite odd way to remove an extension.
Firstly, use of Substring(0,idx) is odd, as there's a Remove(idx)(link) which does exactly that:
int i = list1[n].LastIndexOf(".");
if (i > 0)
list1[n] = list1[n].Remove(i);
But, sencondly, there's even better way of doing it: the System.IO.Path class provides you with a set of well written static methods that, for example, remove the extension (edit: this is what L-Three suggested in comments), with full handling of dots and etc:
var str = System.IO.Path.GetFileNameWithoutExtension("myfile.txt"); // == "myfile"
See MSDN link
It still returns a copy and you still have to store the result somewhere!
list1[n] = Path.GetFileNameWithoutExtension( list1[n] );
Try like below ite will help you....
Description : Filename without Extension
listBox1.Items.Add(Path.GetFileNameWithoutExtension(list1[n]));
Use Path.GetFileNameWithoutExtension
Use Path.GetFileNameWithoutExtension method. Quite easy I guess.
http://msdn.microsoft.com/en-us/library/system.io.path.getfilenamewithoutextension.aspx
Not sure how you've implemented your directory searching, but you can leverage LINQ to your advantage in these situations for clean, easy to read code:
var files = Directory.EnumerateFiles(#"\\PathToFiles")
.Select(f => Path.GetFileNameWithoutExtension(f));
If you're using .NET 4.0, Enumerate files seems to be a superior choice over GetFiles. However it also sounds like you want to get both the full file path and the file name without extension. Here's how you could create a Dictionary so you'd eliminate looping through the collection twice:
var files = Directory.EnumerateFiles(#"\\PathToFiles")
.ToDictionary(f => f, n => Path.GetFileNameWithoutExtension(n));
A way to do this if you don't have a file path, just a file Name
string filePath = (#"D:/" + fileName);
string withoutExtension = Path.getFileNameWithoutExtension(filePath);