I have a snippet of code that will traverse a directory location and create a data model from it. For example, if I have a directory structure:
c:\TestDir1
c:\TestDir1\Sub1\
c:\TestDir1\Sub1\File1.txt
c:\TestDir1\Sub1\File2.txt
c:\TestDir1\Sub1\SubSub1
c:\TestDir1\Sub1\SubSub1\File3.xlsx
c:\TestDir1\Sub1\SubSub1\SubDirX
c:\TestDir1\Sub1\SubSub1\SubDirX\File4.txt
c:\TestDir1\Sub1\SubSub1\SubDirX\File5.txt
c:\TestDir1\Sub1\SubSub1\SubDirX\File6.txt
It will create the appropriate data model via the following code:
static void BeginIt()
{
DirectoryInfo diTop = new DirectoryInfo(#"c:\Misc\3) Projects\002) Document Manager\DirectoryReading\TestDir1");
string path = diTop.FullName;
MySubDir mySubDir = new MySubDir(path);
}
public class MySubDir
{
public ArrayList _dirs;
public ArrayList _files;
public MySubDir(string dirPath)
{
_dirs = new ArrayList();
_files = new ArrayList();
this.ProcessDirectory(dirPath);
}
private void ProcessDirectory(string dirPath)
{
// Process the list of files found in the directory.
string[] fileEntries = Directory.GetFiles(dirPath);
foreach (string fileName in fileEntries)
{
_files.Add(fileName);
}
// Recurse into subdirectories of this directory.
string[] subdirectoryEntries = Directory.GetDirectories(dirPath);
foreach (string subdirectory in subdirectoryEntries)
{
_dirs.Add(new MySubDir(subdirectory));
}
}
}
Here's my question. When I step through the code line by line it is building up the data model appropriately. When I do an AddWatch I can see the object and the directory structure is built up properly.
When I try to access the value of the contents via the Immediate Window I get errors. For example if I type the following into the immediate window the following
? mySubDir._dirs[0]._dirs[0]
I get an error.
How do I get at the values of these subdirectories? I would like to be able to access the directory names and filenames of the elements in this model now that it is created.
Thanks
That doesn't look like it would work, since the expression mySubDir.whatever depends on mySubDir being in scope and having a valid value. In order for that to happen, the constructor has to return first -- but the object is being populated during the execution of the constructor. So there's really no point during the lifetime of this program that such an expression would yield a meaningful result.
If you break into the debugger inside the ProcessDirectory method, you can use this._dirs to have a look into the data structure.
Apart from that, ArrayList is not the best choice for a collection that you know from beforehand will contain just strings, like the ones you have here. It would be more appropriate to define those as System.Collections.Generic.List<string>.
well.. _dirs and files are arraylist.. so you might want to transverse that list and get all the values. a for, an enumerator, a linq or whatever method you like will do the trick..
Update:
After reading some more your post, I think there is a problem of basic understand. Adding just names to the class, will not give you the file position or it folder. You will have to look for a better way to use it (maybe a class folder/files that can hold folders also?)..
_dirs is an ArrayList which stores objects so you need to cast the object from the first _dir[0] to a MySubDir
e.g.
((MySubDir)mySubDir._dir[0])._dir[0]
Either that or change the collection type from ArrayList to
List<MySubDir>
this will give you strongly typed list items when accessed with the indexer.
Related
I'm working on a program that is supposed to scan a specific directory looking for any directories within it that have specific names, and if it finds them, tell the user.
Currently, the way I am loading the names its searching for is like this:
static string path = Path.Combine(Directory.GetCurrentDirectory(), #"database.txt");
static string[] database = File.ReadAllLines(datapath);
I am using this as an array of names to look for when looking through a specific directory. I am doing so with a foreach method.
System.IO.DirectoryInfo di = new DirectoryInfo("C:\ExampleDirectory");
foreach (DirectoryInfo dir in di.GetDirectories())
{
}
Is there a way to see if any of the names in the file "database.txt" match any names of directories found within "C:\ExampleDirectory"?
The only way I can think of doing this is:
System.IO.DirectoryInfo di = new DirectoryInfo(versionspath);
foreach (DirectoryInfo dir in di.GetDirectories())
{
if(dir.Name == //Something...) {
Console.WriteLine("Match found!");
break;}
}
But this obviously won't work, and I cannot think of any other way to do this. Any help would be greatly appreciated!
Based on your other questions on stackoverflow, I presume your question is a homework or you are a passionate hobby programmer, am I right? So I'll try to explain the principle here continuing your almost complete solution.
You will need a nested loop here, a loop in a loop. In the outer loop you iterate through the directories. You already got this one. For each directory you need to loop through the names in database to see if any item in it matches the name of the directory:
System.IO.DirectoryInfo di = new DirectoryInfo(versionspath);
foreach (DirectoryInfo dir in di.GetDirectories())
{
foreach (string name in database)
{
if (dir.Name == name)
{
Console.WriteLine("Match found!");
break;
}
}
}
Depending on your goal, you might want to exit at the first matching directory. The sample code above doesn't. The single break; statement only exits the inner loop, not the outer one. So it continues to check the next directory. Try to figure it out yourself how to stop at the first match (by exiting the outer loop).
As usual, LINQ is the way to go. Whenever you have to find matches or not-matches between two lists and both lists containing different types, you'll have to use .Join() or .GroupJoin().
The .Join() comes into play, if you need to find a 1:1 relationship and the .GroupJoin() for any kind of 1-to relationship (1:0, 1:many or also 1:1).
So, if you need the directories that match your list, this sounds for a job to the .Join() operator:
public static void Main(string[] args)
{
// Where ever this comes normally from.
string[] database = new[] { "fOo", "bAr" };
string startDirectory = #"D:\baseFolder";
// A method that returns an IEnumerable<string>
// Using maybe a recursive approach to get all directories and/or files
var candidates = LoadCandidates(startDirectory);
var matches = database.Join(
candidates,
// Simply pick the database entry as is.
dbEntry => dbEntry,
// Only take the last portion of the given path.
fullPath => Path.GetFileName(fullPath),
// Return only the full path from the given matching pair.
(dbEntry, fullPath) => fullPath,
// Ignore case on comparison.
StringComparer.OrdinalIgnoreCase);
foreach (var match in matches)
{
// Shows "D:\baseFolder\foo"
Console.WriteLine(match);
}
Console.ReadKey();
}
private static IEnumerable<string> LoadCandidates(string baseFolder)
{
return new[] { #"D:\baseFolder\foo", #"D:\basefolder\baz" };
//return Directory.EnumerateDirectories(baseFolder, "*", SearchOption.AllDirectories);
}
You can use LINQ to do this
var allDirectoryNames = di.GetDirectories().Select(d => d.Name);
var matches = allDirectoryNames.Intersect(database);
if (matches.Any())
Console.WriteLine("Matches found!");
In the first line we get all the directory names, then we use the Intersect() method to see which ones are present in both allDirectoryNames and database
I am trying to make a class which will help me delete one specific line from a file. So I came up with the idea to put all lines in an arraylist, remove from the list the line i don't need, wipe clear my .txt file and write back the remaining objects of the list. My problem is that I encounter some sort of logical error i can't fint, that doesn't remove the line from the arraylist and writes it back again. Here's my code:
public class delete
{
public void removeline(string line_to_delete)
{
string[] lines = File.ReadAllLines("database.txt");
ArrayList list = new ArrayList(lines);
list.Remove(line_to_delete);
File.WriteAllText("database.txt", String.Empty);
using (var writer = new StreamWriter("database.txt"))
{
foreach (object k in lines)
{
writer.WriteLine(k);
}
}
}
}
What is that I am missing? I tried lots of things on removing a line from a text file that did not work. I tried this because it has the least file operations so far.
Thanks!
You can do:
var line_to_delete = "line to delete";
var lines = File.ReadAllLines("database.txt");
File.WriteAllLines("database.txt", lines.Where(line => line != line_to_delete));
File.WriteAllLines will overwrite the existing file.
Do not use ArrayList, there is a generic alternative List<T>. Your code is failing due to the use of ArrayList as it can only remove a single line matching the criteria. With List<T> you can use RemoveAll to remove all the lines matching criteria.
If you want to do the comparison with ignore case you can do:
File.WriteAllLines("database.txt", lines.Where(line =>
!String.Equals(line, line_to_delete, StringComparison.InvariantCultureIgnoreCase)));
I believe you're intending:
public static void RemoveLine(string line)
{
var document = File.ReadAllLines("...");
document.Remove(line);
document.WriteAllLines("..");
}
That would physically remove the line from the collection. Another approach would be to simply filter out that line with Linq:
var filter = document.Where(l => String.Compare(l, line, true) == 0);
You could do the Remove in an ArrayList proper documentation on how is here. Your code should actually work, all of these answers are more semantic oriented. The issue could be due to actual data, could you provide a sample? I can't reproduce with your code.
I want to modify some strings that are contained in an object like say an array, or maybe the nodes in an XDocument (XText)XNode.Value.
I want to gather a subset of strings from these objects and modify them, but I don't know at runtime from what object type they come from.
Put another way, let's say I have objects like this:
List<string> fruits = new List<string>() {"apple", "banana", "cantelope"};
XDocument _xmlObject;
I want to be able to add a subset of values from the original collections to new lists like this:
List<ref string> myStrings1 = new List<ref string>();
myStrings1.Add(ref fruits[1]);
myStrings1.Add(ref fruits[2]);
List<ref string> myStrings2 = new List<ref string>();
IEnumerable<XNode> xTextNodes = getTargetTextNodes(targetPath); //some function returns a series of XNodes in the XDocument
foreach (XNode node in xTextNodes)
{
myStrings2.Add(((XText)node).Value);
}
Then change the values using a general purpose method like this:
public void Modify(List<ref string> mystrings){
foreach (ref string item in mystrings)
{
item = "new string";
}
}
Such that I can pass that method any string collection, and modify the strings in the original object without having to deal with the original object itself.
static void Main(string[] args)
{
Modify(myStrings1);
Modify(myStrings2);
}
The important part here is the mystrings collection. That can be special. But I need to be able to use a variety of different kinds of strings and string collections as the originals source data to go in that collection.
Of course, the above code doesn't work, and neither does any variation I've tried. Is this even possible in c#?
What you want is possible with C#... but only if you can fix every possible source for your strings. That would allow you to use pointers to the original strings... at a terrible cost, however, in terms of memory management and unsafe code throughout your application.
I encourage you to pursue a different direction for this.
Based on your edits, it looks like you're always working with an entire collection, and always modifying the entire collection at once. Also, this might not even be a string collection at the outset. I don't think you'll be able to get the exact result you want, because of the base XDocument type you're working with. But one possible direction to explore might look like this:
public IEnumerable<string> Modify(IEnumerable<string> items)
{
foreach(string item in items)
{
yield return "blah";
}
}
You can use a projection to get strings from any collection type, and get your modified text back:
fruits = Modify(fruits).ToList();
var nodes = Modify( xTextNodes.Select(n => (XText)n.Value));
And once you understand how to make a projection, you may find that the existing .Select() method already does everything you need.
What I really suggest, though, is that rather than working with an entire collection, think about working in terms of one record at a time. Create a common object type that all of your data sources understand. Create a projection from each data source into the common object type. Loop through each of the objects in your projection and make your adjustment. Then have another projection back to the original record type. This will not be the original collection. It will be a new collection. Write your new collection back to disk.
Used appropriately, this also has the potential for much greater performance than your original approach. This is because working with one record at a time, using these linq projections, opens the door to streaming the data, such that only one the one current record is ever held in memory at a time. You can open a stream from the original and a stream for the output, and write to the output just as fast as you can read from the original.
The easiest way to achieve this is by doing the looping outside of the method. This allows you to pass the strings by reference which will replace the existing reference with the new one (don't forget that strings are immutable).
And example of this:
void Main()
{
string[] arr = new[] {"lala", "lolo"};
arr.Dump();
for(var i = 0; i < arr.Length; i++)
{
ModifyStrings(ref arr[i]);
}
arr.Dump();
}
public void ModifyStrings(ref string item)
{
item = "blah";
}
I am working in C#. I have a segment of code that returns the file as well as path of a specific file type and places them inside a select list
private void Form1_Load(object sender, EventArgs e)
{
// Only get .sde files
string[] dirs = System.IO.Directory.GetFiles(#"c:\Users\JohnDoe\Desktop\my_files", "*.sde");
this.GetSdePath.Items.AddRange(dirs);
}
When I run my program, the select list contains all the sde files. They are listed/displayed as such:
c:\Users\JohnDoe\Desktop\my_files\NewCreated.sde
c:\Users\JohnDoe\Desktop\my_files\Inventory.sde
c:\Users\JohnDoe\Desktop\my_files\Surplus.sde
c:\Users\JohnDoe\Desktop\my_files\Logistics.sde
I am wondering if in my select list is it possible to hide the path and just display the name of the sde file. So the list would look like
NewCreated.sde
Inventory.sde
Surplus.sde
Logistics.sde
BUT, each value in the list would return the full path and name.
Any help on this topic would be greatly appreciated. Thanks in advance.
Use Path.GetFileName(string path)
private void Form1_Load(object sender, EventArgs e)
{
// Only get .sde files
string[] dirs = System.IO.Directory.GetFiles(#"c:\Users\JohnDoe\Desktop\my_files", "*.sde");
this.GetSdePath.Items.AddRange(dirs.Select(path => Path.GetFileName(path).ToArray());
}
Using Select on the sequence returned to apply the Path.GetFileName method that extracts just the filename from the fullpath
var dirs = System.IO.Directory.GetFiles(#"c:\Users\JohnDoe\Desktop\my_files", "*.sde")
.Select (d => Path.GetFileName(d));
this.GetSdePath.Items.AddRange(dirs.ToArray());
I don't know how many files are present in your folder but probably it is better to use EnumerateFiles instead of GetFiles
var dirs = System.IO.Directory.EnumerateFiles(#"c:\Users\JohnDoe\Desktop\my_files", "*.sde")
.Select (d => Path.GetFileName(d));
MSDN says
The EnumerateFiles and GetFiles methods differ as follows: When you
use EnumerateFiles, you can start enumerating the collection of names
before the whole collection is returned; when you use GetFiles, you
must wait for the whole array of names to be returned before you can
access the array. Therefore, when you are working with many files and
directories, EnumerateFiles can be more efficient.
EDIT
Following your comments below the choice of EnumerateFiles is not possible (available from NET 4.0) and if you want to keep the full path name available for other tasks but show just the filename in the listbox then you need to keep it in some kind of collection (an array or better a list)
using System.IO;
...
string sourcePath = #"c:\Users\JohnDoe\Desktop\my_files";
List<string> dirs = Directory.GetFiles(sourcePath, "*.sde")
.Select (d => Path.GetFileName(d)
.ToList());
this.GetSdePath.Items.AddRange(dirs.ToArray());
;
Make List<string>dirs a form level variable if you need its content outside the Form_Load event
Currently I am using the following code to search for files in a folder:
public string[] getFiles(string SourceFolder, string Filter,System.IO.SearchOption searchOption)
{
// ArrayList will hold all file names
ArrayList alFiles = new ArrayList();
// Create an array of filter string
string[] MultipleFilters = Filter.Split('|');
// for each filter find mathing file names
foreach (string FileFilter in MultipleFilters)
{
// add found file names to array list
alFiles.AddRange(Directory.GetFiles(SourceFolder, FileFilter, searchOption));
}
// returns string array of relevant file names
return (string[])alFiles.ToArray(typeof(string));
}
The problem is that when I pass a drive like D:\\ as the path to search, either I get an exception in GetFiles() or nothing is found!
I also get exceptions when I try to access some hidden or system secured folder.
How can I properly search for files in a drive or folder recursively?
One more thing, I come to know that a extension like "abc" may return files with having extensions like "abcd" or "abcde".
If this is true, how can I overcome this problem?
Thank you.