I have many prompts to be used in the code, each prompt is a string such as "welcome to our company" or "goodby" etc.
Now I want to manage these prompts. There are two ways, one is to store each string in a file then load it in the code.
private string LoadPrompt(string inid, string PromptsPath,string promptFile)
{
StringBuilder filetopen = new StringBuilder();
StringBuilder content = new StringBuilder();
filetopen.Append(PromptsPath + inid + "_" + promptFile);
try
{
if (File.Exists(filetopen.ToString()))
{
using (StreamReader reader = File.OpenText(filetopen.ToString()))
{
string line;
while ((line = reader.ReadLine()) != null)
{
content.Append(line);
}
}
}
}
catch (Exception ex)
{
AppLogEx(ex, "Utilities:LoadPrompt");
content.Clear();
content.Append("ERROR");
}
return content.ToString();
}
The other one is put prompts in app.config. Which way is better and fast to load them?
Set up the strings as Resources.
The advantage to using resources over a flat file or app.config is that you can then localize your strings into various languages, and there is support and tooling available to make this process much easier. If you provide multiple languages, your program can automatically select the appropriate language/culture based on the local system without you needing to do any extra work.
About the code, you can do this simpler
instead
using (StreamReader reader = File.OpenText(filetopen.ToString()))
{
string line;
while ((line = reader.ReadLine()) != null)
{
content.Append(line);
}
}
you can write
File.ReadAllLines(filetopen.ToString())
.ToList() - added only for Foreach, I think you've your own Foreach extension for IEnumerable
.ForEach(x => content.AppendLine(x));
about prompts, config or resources
Well, if you do it through app.config, it's essentially an XML file. So you'll be able to find the right prompt fairly easily. Since it's a setting file, it would make sense to store in it any settings that you might change down the road. You can do the same thing with a regular text file, but then you'll need to somehow mark different prompts... in which case, might as well use XML.
Related
I'm currently working on a dll library project.
if (!Directory.Exists(MenGinPath))
{
Directory.CreateDirectory(MenGinPath + #"TimedMessages");
File.WriteAllLines(MenGinPath + #"TimedMessages\timedmessages.txt", new string[] { "Seperate each message with a new line" });
}
else if (!File.Exists(MenGinPath + #"TimedMessages\timedmessages.txt"))
{
Directory.CreateDirectory(MenGinPath + #"TimedMessages");
File.WriteAllLines(MenGinPath + #"TimedMessages\timedmessages.txt", new string[] { "Seperate each message with a new line" });
}
As you can see if the statement Directory.Exists is false a specific directory (MenGinPath) will be created. However, if the same path, with another file in addition is false, the second functions will be called.
My question is the following: is there any way to make this shorter?
Because as you can see I'm calling 2 times the same functions:
Directory.CreateDirectory(MenGinPath + #TimedMessages\timedmessages.txt
and
File.WriteAllLines(MenGinPath + #"\TimedMessages\timedmessages.txt"))
Any help would be welcome!!
You don't need to check if directory exists because Directory.CreateDirectory automatically creates the directory if it does not exists and does nothing if the directory already exists.
Also, do not include the filename when creating the directory. Yes, it wont error but just for clarity sake.
Another one is to use Path.Combine instead of hardcoding the path. This will improve readability of your code.
So, here's what I can come up with:
string dir = Path.Combine(MenGinPath, #"Groups\TimesMessages");
string file = Path.Combine(dir, "timedmessages.txt");
// this automatically creates all directories in specified path
// unless it already exists
Directory.CreateDirectory(dir);
//of course, you still need to check if the file exists
if (!File.Exists(file) {
File.WriteAllLines(filePath, new string[] { "Seperate each message with a new line" });
}
/* or if file exists, do your stuff (optional)
* else {
* //do something else? maybe edit the file?
* }
*/
You can make your code shorter given the fact that CreateDirectory does nothing when the directory exists. Moreover do not pullute your code with all that string concatenations to create the path and the file names.
Just do it one time before entering the logic using the appropriate method to create filenames and pathnames (Path.Combine).
string messagePath = Path.Combine(MenGinPath, "TimedMessages");
string fileName = Path.Combine(messagePath, "timedmessages.txt");
// call the create even if it exists. The CreateDirectory checks the fact
// by itself and thus, if you add your own check, you are checking two times.
Directory.CreateDirectory(messagePath);
if (!File.Exists(fileName)
File.WriteAllLines(fileName, new string[] { "Seperate each message with a new line" });
Would something like this work?
string strAppended = string.Empty;
if (!Directory.Exists(MenGinPath))
{
strAppended = MenGinPath + #"Groups\timedmessages.txt";
}
else if (!File.Exists(MenGinPath + #"TimedMessages\timedmessages.txt"))
{
strAppended = MenGinPath + #"TimedMessages\TimedMessages.txt";
}
else
{
return;
}
Directory.CreateDirectory(strAppended);
File.WriteAllLines(strAppended, new string[] { "Seperate each message with a new line" });
I have found that it is a great idea to reuse blocks of code like this instead of hiding them in if statements because it makes code maintenance and debugging easier and less prone to missed bugs.
It seems the only difference between the 2 cases is the path. So just get only this path in your if-else
const string GroupsPath = #"Groups\timedmessages.txt";
const string TimedMessagesTxt = #"TimedMessages\TimedMessages.txt";
string addPath = null;
if (!Directory.Exists(MenGinPath)) {
addPath = GroupsPath;
} else if (!File.Exists(Path.Combine(MenGinPath, TimedMessagesTxt))) {
addPath = TimedMessagesTxt;
}
If (addPath != null) {
Directory.CreateDirectory(Path.Combine(MenGinPath, addPath));
File.WriteAllLines(Path.Combine(MenGinPath, TimedMessagesTxt),
new string[] { "Seperate each message with a new line" });
}
Note: Using Path.Combine instead of string concatenation has the advantage that missig or extra \ are added or removed automatically.
No shortage of search for string performance questions out there yet I still can not make heads or tails out of what the best approach is.
Long story short, I have committed to moving from 4NT to PowerShell. In leaving the 4NT I am going to miss the console super quick string searching utility that came with it called FFIND. I have decided to use my rudimentary C# programming skills to try an create my own utility to use in PowerShell that is just as quick.
So far search results on a string search in 100's of directories across a few 1000 files, some of which are quite large, are FFIND 2.4 seconds and my utility 4.4 seconds..... after I have ran mine at least once????
The first time I run them FFIND does it near the same time but mine takes over a minute? What is this? Loading of libraries? File indexing? Am I doing something wrong in my code? I do not mind waiting a little longer but the difference is extreme enough that if there is a better language or approach I would rather start down that path now before I get too invested.
Do I need to pick another language to write a string search that will be lighting fast
I have the need to use this utility to search through 1000 of files for strings in web code, C# code, and another propitiatory language that uses text files. I also need to be able to use this utility to find strings in very large log files, MB size.
class Program
{
public static int linecounter;
public static int filecounter;
static void Main(string[] args)
{
//
//INIT
//
filecounter = 0;
linecounter = 0;
string word;
// Read properties from application settings.
string filelocation = Properties.Settings.Default.FavOne;
// Set Args from console.
word = args[0];
//
//Recursive search for sub folders and files
//
string startDIR;
string filename;
startDIR = Environment.CurrentDirectory;
//startDIR = "c:\\SearchStringTestDIR\\";
filename = args[1];
DirSearch(startDIR, word, filename);
Console.WriteLine(filecounter + " " + "Files found");
Console.WriteLine(linecounter + " " + "Lines found");
Console.ReadKey();
}
static void DirSearch(string dir, string word, string filename)
{
string fileline;
string ColorOne = Properties.Settings.Default.ColorOne;
string ColorTwo = Properties.Settings.Default.ColorTwo;
ConsoleColor valuecolorone = (ConsoleColor)Enum.Parse(typeof(ConsoleColor), ColorOne);
ConsoleColor valuecolortwo = (ConsoleColor)Enum.Parse(typeof(ConsoleColor), ColorTwo);
try
{
foreach (string f in Directory.GetFiles(dir, filename))
{
StreamReader file = new StreamReader(f);
bool t = true;
int counter = 1;
while ((fileline = file.ReadLine()) != null)
{
if (fileline.Contains(word))
{
if (t)
{
t = false;
filecounter++;
Console.ForegroundColor = valuecolorone;
Console.WriteLine(" ");
Console.WriteLine(f);
Console.ForegroundColor = valuecolortwo;
}
linecounter++;
Console.WriteLine(counter.ToString() + ". " + fileline);
}
counter++;
}
file.Close();
file = null;
}
foreach (string d in Directory.GetDirectories(dir))
{
//Console.WriteLine(d);
DirSearch(d,word,filename);
}
}
catch (System.Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
If you want to speed up your code run a performance analysis and see what is taking the most time. I can almost guaruntee the longest step here will be
fileline.Contains(word)
This function is called on every line of the file, on every file. Naively searching for a word in a string can taken len(string) * len(word) comparisons.
You could code your own Contains method, that uses a faster string comparison algorithm. Google for "fast string exact matching". You could try using a regex and seeing if that gives you a performance enhancement. But I think the simplest optimization you can try is :
Don't read every line. Make a large string of all the content of the file.
StreamReader streamReader = new StreamReader(filePath, Encoding.UTF8);
string text = streamReader.ReadToEnd();
Run contains on this.
If you need all the matches in a file, then you need to use something like Regex.Matches(string,string).
After you have used regex to get all the matches for a single file, you can iterate over this match collection (if there are any matches). For each match, you can recover the line of the original file by writing a function that reads forward and backward from the match object index attribute, to where you find the '\n' character. Then output that string between those two newlines, to get your line.
This will be much faster, I guarantee it.
If you want to go even further, some things I've noticed are :
Remove the try catch statement from outside the loop. Only use it exactly where you need it. I would not use it at all.
Also make sure your system is running, ngen. Most setups usually have this, but sometimes ngen is not running. You can see the process in process explorer. Ngen generates a native image of the C# managed bytecode so the code does not have to be interpreted each time, but can be run natively. This speeds up C# a lot.
EDIT
Other points:
Why is there a difference between first and subsequent run times? Seems like caching. The OS could have cached the requests for the directories, for the files, for running and loading programs. Usually one sees speedups after a first run. Ngen could also be playing a part here, too, in generating the native image after compilation on the first run, then storing that in the native image cache.
In general, I find C# performance too variable for my liking. If the optimizations suggested are not satisfactory and you want more consistent performance results, try another language -- one that is not 'managed'. C is probably the best for your needs.
I am a beginner at Programming with C Sharp, and I am using the XNA SDK. I am trying to make a simple game to help my fellow classmates with studying for school, and I decided I would like it if there was some way to have them put music they want to listen to while playing the game inside of a file, and have the game automatically Load the music files, and play them in a playlist.
So far, I am able to get the game to detect whether the files are music, by detecting whether the file path name Contains(".mp3") , but I am trying to actually load the file name into a list of type Song, using a for Loop. The code looks like this.
(Declaration)
List<Song> songsToPlay;
string[] fileNames
(Initialize)
fileNames[] = Directory.GetFiles(".\Music")
(LoadContent)
for (int i = 0; i < fileNames.Count(); i++)
{
if (fileNames[i].Contains(".mp3")
{
songsToPlay.Add(fileNames[i]);
}
}
I have been trying to find a way to add a whole folder to the Content Directory, and have it do something more like
for (int i = 0; i < fileNames.Count(); i++)
{
songsToPlay.Add(Content.Load<Song>("fileNames[i]")
}
I have been unable to find some way to do this... Does anyone know how to make this work, or a better way to do this?
If you have your files in your project content, you should use the ContentManager class. It gives you more than just file loading. For example you can use Content.Unload to unload all your data when you're no longer using it.
There is no need to avoid that class. This page has an example showing exactly what you're trying to do:
public static Dictionary<string, T> LoadContent<T>(
this ContentManager contentManager, string contentFolder)
{
var dir = new DirectoryInfo(contentManager.RootDirectory
+ "\\" + contentFolder);
if (!dir.Exists)
throw new DirectoryNotFoundException();
var result = new Dictionary<string, T>();
foreach (FileInfo file in dir.GetFiles("*.mp3"))
{
string key = Path.GetFileNameWithoutExtension(file.Name);
result[key] = contentManager.Load<T>(
contentManager.RootDirectory + "/" + contentFolder + "/" + key);
}
return result;
}
You can use it like this:
var songs = Content.LoadContent<Song>("Songs");
Slight improvement to this code...
Once you get the above code working, I also suggest you make a slight change:
var dir = new DirectoryInfo(
System.IO.Path.Combine(contentManager.RootDirectory, contentFolder));
You shouldn't manually build paths via string concatenation when you can possibly avoid it. I don't know that you can do the same for ContentManager paths tho, so you might have to stick with string concatenation for that case.
Edit: Too many constructs you haven't used in class yet
Since you haven't used extension methods or the static keyword in your class yet, and probably haven't used dictionaries, here's a simpler way to do this:
string contentFolder = "Music";
var dir = new DirectoryInfo(Content.RootDirectory + "\\" + contentFolder);
if (!dir.Exists)
{
// Todo: Display a message to the user instead?
throw new DirectoryNotFoundException();
}
string[] files = dir.GetFiles("*.mp3");
for (int i = 0; i < files.Count(); ++i)
{
FileInfo file = files[i];
string key = System.IO.Path.GetFileNameWithoutExtension(file.Name);
var song = Content.Load<Song>(
Content.RootDirectory + "/" + contentFolder + "/" + key);
songsToPlay.Add(song);
}
Edit2: Some explanation of this second code sample
The DirectoryInfo class lets you load up a directory so you can enumerate all the files in it.
The GetFiles method on DirectoryInfo lets you enumerate files using a wildcard style pattern matching. Wildcard pattern matching for files means that when given these patterns:
*.* - you are looking for files named <anything>.<anything>
*.mp3 - you are looking for <anything>.mp3
throw means throwing an exception. This will deliberately stop executing code and display a good error message ("directory not found") and a line number. There is a lot to learn about exception handling, so I won't try to do it justice with a description here.
GetFileNameWithoutExtension should be obvious because it is well named.
Content.RootDirectory + "/" + contentFolder + "/" + key
That last little bit of code will build up a string containing the content root directory, the sub-directory of your songs, and each file name, using a name it can understand (since it doesn't know about filename extensions).
var means "whatever type I assign to it". It is a short-cut. For example, instead of typing:
List<string> someList = new List<string>();
You type:
var someList = new List<string>();
var has to know what type is on the right-hand-side of the assignment. It is useful because you can avoid repeating yourself.
Using var doesn't bestow any magical abilities to the variable though. You won't be able to assign a variable of a different type once you've declared the variable. It is just a short-cut for the exact same functionality.
Use the Song.FromUri method:
Song.FromUri("audio name", new Uri(#"C:\audio.mp3"));
The filepath can't contain spaces!
See here for a workaround: XNA 4 Song.fromUri containing Spaces
Okay so i'm trying to load up a bunch of profiles through C# and I keep getting this error when I try to start up the program.
C:\C#FILES>program.exe
Unhandled Exception: System.IndexOutOfRangeException: Index was outside the boun
ds of the array.
at ConsoleApplication2.Program.loadAccounts()
at ConsoleApplication2.Program.Main(String[] args)
C:\C#FILES>
i've investigated and i think it has to do with the format of the accounts in the file
i'm wondering what the proper way is, i've tried every way i can think of
here's the loading accounts method
private static void loadAccounts()
{
using (TextReader tr = new StreamReader("accounts.txt"))
{
string line = null;
while ((line = tr.ReadLine()) != null)
{
String[] details = line.Split('\t');
accounts.Add(details[0] + ":" + details[1]);
}
}
}
the accounts.txt part is the part i'm unsure about, i thought it would be as follows
username(tab)password
like this
username password
however it gives the error shown above
does anyone know what the proper account format should be?
You're getting an IndexOutOfRangeException, which suggests that details only had a single entry - which means there wasn't a tab on that line.
I suggest you print out the line in question before splitting, so you can see which line is causing problems. Or possibly do it conditionally:
while ((line = tr.ReadLine()) != null)
{
String[] details = line.Split('\t');
if (details.Length == 1)
{
// Or log it, or whatever...
Console.WriteLine("Input error: no tab in line '{0}'", line);
}
else
{
accounts.Add(details[0] + ":" + details[1]);
}
}
This is occurring because the line you are splitting from your input does not contain the elements requested.
It is unlikely that the first (read: 0th) element in the array is the cause of the issue because of the way that .NET deals with Split.
Have you check that there are no blank lines in your input file? A single blank line (even at the end of the file) would cause this issue.
There are multiple checks you could add such as..
if(!string.IsNullOrWhitespace(line)) ...
or
if(details.Length > 1)
These are a few checks, either or both I would recommend implementing (there are more to consider) otherwise you are just blindly trusting input values and that is not good practice in general.
I have an INI file that I want to read the line DeviceName=PHJ01444-MC35 from group [DEVICE] and assign the value to a string.
[BEGIN-DO NOT CHANGE/MOVE THIS LINE]
[ExecList]
Exec4=PilotMobileBackup v1;Ver="1.0";NoUninst=1
Exec3=MC35 108 U 02;Ver="1.0.8";NoUninst=1
Exec2=FixMGa;Ver="1.0";Bck=1
Exec1=Clear Log MC35;Ver="1.0";Bck=1
[Kiosk]
Menu8=\Program Files\PilotMobile\Pilot Mobile Backup.exe
MenuCount=8
AdminPwd=D85F72A85AE65A71BF3178CC378B260E
MenuName8=Pilot Mobile Backup
Menu7=\Windows\SimManager.exe
MenuName7=Sim Manager
UserPwd=AF2163B24AF45971
PasswordPolicy=C34B3DE916AA052DCB2A63D7DCE83F17
DisableBeam=0
DisableBT=0
DisableSDCard=0
EnableAS=1
ActionCount=0
Url=file://\Application\MCPortal.htz
AutoLaunch=0
Menu6=\Windows\solitare.exe
MenuName6=Solitare
Menu5=\Windows\bubblebreaker.exe
MenuName5=Bubble Breaker
Menu4=\Windows\wrlsmgr.exe
MenuName4=Communications
Menu3=\Windows\Calendar.exe
MenuName3=Calendar
Menu2=\Windows\tmail.exe
MenuName2=Text Messaging
Menu1=\Program Files\PilotMobile\Pilot.Mobile.exe
MenuName1=Pilot Mobile
ShowStartMenu=1
CustomTaskBar=0
IdleTimeout=0
NoTaskbar=0
PPCKeys=1111111111111111
On=1
[Status]
MCLastConn=2006/10/01 00:50:56
[Connection]
DeploySvr1=********
[Locations]
Backup=Backup
Install=\Application
[Comm]
RetryDelay=60000
NoInBoundConnect=0
TLS=0
Broadcast=1
[Info]
LID=090128-117
PWDID=081212-10
TimeSyncID={249CEE72-5918-4D18-BEA8-11E8D8D972BF}
TimeSyncErrorInterval=5
TimeSyncInterval=120
AutoTimeSync=1
SecondarySNTPServer=ntp1.uk.uu.net
DefaultSNTPServer=ntp0.uk.uu.net
DepServerTimeSyncType=4
TimeSyncServerType=1
DFID=080717-8
Platform=PPC
Method=39
SiteName=*****
[Device]
SyncTimer=4
Ver=1
DeviceID={040171BD-3603-6106-A800-FFFFFFFFFFFF}
ShowTrayIcon=1
DeviceIDType=2
DeviceClass=AADE7ECE-DF8C-4AFC-89D2-DE7C73B579D0
DeviceName=PHJ01444-MC35
NameType=2
[END-DO NOT CHANGE/MOVE THIS LINE]
You could use Windows API for this. See http://jachman.wordpress.com/2006/09/11/how-to-access-ini-files-in-c-net/
Edit: As noted in the comments the page is no longer available on the original site, however it's still accessible on the Wayback Machine.
Additionally there is a more recent article on MSDN about accessing the required functions.
Because writing everything in one line makes me a better person than you:
string s = File.ReadAllText("inifile.ini").Split('\r', '\n').First(st => st.StartsWith("DeviceName"));
If you wanted the very simple but not very clean answer:
using System.IO;
StreamReader reader = new StreamReader(filename);
while(reader.ReadLine() != "[DEVICE]") { continue; }
const string DeviceNameString = "DeviceName=";
while(true) {
string line = reader.ReadLine();
if(line.Length < DeviceNameString.Length) { continue; }
else if(line.Substring(0, DeviceNameString.Length) != DeviceNameString) { continue; }
return line.Substring(DeviceNameString.Length);
}
If you're only intending to read one value from the file, it's a plausible option. I would probably combine the loops and add for some end of file checking though myself if you're serious about using this code.
string line;
string deviceName = string.Empty;
// Read the file and display it line by line.
using (System.IO.StreamReader file =
new System.IO.StreamReader("c:\\file.ini"))
{
while ((line = file.ReadLine()) != null)
{
if (line.ToLower().StartsWith("devicename"))
{
string[] fullName = line.Split('=');
deviceName = fullName[1];
break;
}
}
}
Console.WriteLine("Device Name =" + deviceName);
Console.ReadLine();
I am sure there are other ways.