In C#, I have a StringBuilder sb to which I am appending numerous times in for-loops.
Is there is a simple method for StringBuilders that spits out the last string that was appended to it?
Nope. You should probably use a list of strings and then later join it to replace the StringBuilder functionality like this:
List<string> strings = new List<string>();
strings.Add("...");
string result = string.Join("", strings);
Then you can access the most recently added string by accessing the last index of the string list or use the Last() LINQ extension like this:
string lastString = strings.Last();
You could create your own StringBuilder extension method that remembers the last string appended:
public static class StringBuilderExtensions
{
public static string LastStringAppended { get; private set; }
public static StringBuilder AppendRemember(this StringBuilder sb, object obj)
{
string s = obj == null ? "" : obj.ToString();
LastStringAppended = s;
return sb.Append(s);
}
}
Call it like this
sb.AppendRemember("hello").AppendRemember(" world");
string lastString = StringBuilderExtensions.LastStringAppended;
However, note that the last string will be remembered globally. It is not bound to a StringBuilder instance.
If you need to remember the last addition per StringBuilder instance, you can attach an additional "property" to the StringBuilder via a dictionary. I am using this implementation of ObjectReferenceEqualityComparer<T>. It is necessary beacuse StringBuilder overrides Equals and does not use reference equality.
public static class StringBuilderExtensions
{
private static Dictionary<StringBuilder, string> _lastStrings =
new Dictionary<StringBuilder, string>(
new ObjectReferenceEqualityComparer<StringBuilder>());
public static string LastAppended(this StringBuilder sb)
{
string s;
_lastStrings.TryGetValue(sb, out s);
return s; // s is null if not found in the dict.
}
public static StringBuilder AppendRemember(this StringBuilder sb, object obj)
{
string s = obj == null ? "" : obj.ToString();
_lastStrings[sb] = s;
return sb.Append(s);
}
}
Use it like this:
sb.AppendRemember("hello").AppendRemember(" world");
string lastString = sb.LastAppended();
As floele answered, you're probably better off just using a List and then joining it. But as Olivier Jacot-Descombes and Tim Schmelter hinted at in their commeents, you can have your own class with an internal StringBuilder, then you just have to recreate the methods from StringBuilder you want to use, and store the last appended string inside your class in a property. This has two key advantages over Olivier's answer: the remembered string isn't "global" (it's unique to each instance), and the code is many times simpler.
public class StringBuilderRemember
{
private StringBuilder sb=new StringBuilder();
public string LastString {get; private set;}
public void AppendLine(string s)
{
LastString=s;
sb.AppendLine(s);
}
public void Append(string s)
{
LastString=s;
sb.Append(s);
}
public string ToString()
{
return sb.ToString();
}
}
Related
Since Python 3.8 it is possible to use self-documenting expressions in f-strings like this:
>>> variable=5
>>> print(f'{variable=}')
variable=5
is there an equivalent feature in C#?
No, but you can use InterpolatedStringHandler and CallerArgumentExpression to write it yourself:
[InterpolatedStringHandler]
public ref struct SelfDocumentingStringHandler
{
StringBuilder builder;
public SelfDocumentingStringHandler(int literalLength, int formattedCount)
{
builder = new StringBuilder(literalLength);
}
public void AppendLiteral(string s)
{
builder.Append(s);
}
public void AppendFormatted<T>(T t, [CallerArgumentExpression(nameof(t))] string member = "")
{
builder.Append(member + "=");
builder.Append(t);
}
internal string GetFormattedText() => builder.ToString();
}
void Print(ref SelfDocumentingStringHandler stringHandler)
{
Console.WriteLine(stringHandler.GetFormattedText());
}
then you can use it like this:
var variable = 5;
Print($"{variable}"); // prints: variable=5
Yes.
int variable = 5;
Console.WriteLine($"variable={variable}");
That outputs:
variable=5
The key here is the $ that precedes the string literal.
To do what you want with the name coming dynamically, I'd suggest a more explicit approach of using an extension method. Try this:
public static class SelfDocExt
{
public static string SelfDoc<T>(
this T value,
[CallerArgumentExpression(nameof(value))] string name = "")
=> $"{name}={value}";
}
Then you can write this:
int variable = 5;
Console.WriteLine($"{variable.SelfDoc()}");
It solves your problem without breaking string interpolation.
I'm having problem with creating regular expression which would map sql server like clause.
Let's take an example:
There is string C2-513101-0045 in my collection and I would like to filter my collection to get this string by typing e.g : C2-%-0045 or C2-51% etc.
I've tried this...
public static class MyStringExtensions
{
public static bool Like(this string toSearch, string toFind)
{
return new Regex(#"\A" + new Regex(#"\.|\$|\^|\{|\[|\(|\||\)|\*|\+|\?|\\").Replace(toFind, ch => #"\" + ch).Replace('_', '.').Replace("%", ".*") + #"\z", RegexOptions.Singleline).IsMatch(toSearch);
}
}
... and also tried to modify it. No results.
How to achieve filtering strings "sql-like" ?
public static class MyStringExtensions
{
public static bool Like(this string toSearch, string toFind)
{
Regex findRegex = new Regex(Regex.Escape(toFind).Replace("%", ".*"));
return findRegex.IsMatch(toSearch);
}
}
Try the above method. First I have escaped all meta-characters using Regex.Escape. Then replace % by .*
I'd like to parse a text file with a few dozen entries. Right now, I have a dumbed-down solution that reads line by line and compares against hard-coded strings:
while ((line = reader.ReadLine()) != null) //returns null if end of stream
{
cmpStr = "MODE";
try
{
if (line.Equals(cmpStr))
GlobalData.mode = Convert.ToInt32(line.Remove(0, cmpStr.Length));
}
catch { }
cmpStr = "TIME_YEAR";
try
{
if (line.Equals(cmpStr))
GlobalData.time_year = Convert.ToInt32(line.Remove(0, cmpStr.Length));
}
catch { }
// ... repeat to parse the remaining lines
}
GlobalData is a static class and looks like this:
public static class GlobalData
{
public static int mode;
public static int time_year;
public static int time_month;
public static int time_day;
public static int time_hour;
public static int time_minute;
// other entries omitted
public static string[] GlobalKeywords = new string[37]
{
"MODE",
"TIME_YEAR",
"TIME_MONTH",
"TIME_DAY",
"TIME_HOUR",
"TIME_MINUTE",
// other entries omitted
};
}
If it were possible to access my static fields by index, I'd do:
int i = 0;
while ((line = reader.ReadLine()) != null)
{
cmpStr = GlobalData.GlobalKeywords[i]; // when i == 0: cmpStr = "MODE"
if (line.Equals(cmpStr))
GlobalData[i] = Convert.ToInt32(line.Remove(0, cmpStr.Length));
// GlobalData[0] would be GlobalData.mode, and so on (but doesn't work)
i++;
}
catch { }
So, even though I can setup a loop to compare against a string array of keywords,
how do I assign a certain field of my static class ?
br
Chris
I'm not sure what your business constraints are, so it's hard to propose a fool-proof solution, though a few points:
cmpStr = "MODE";
try
{
if (line.Equals(cmpStr))
GlobalData.mode = Convert.ToInt32(line.Remove(0, cmpStr.Length));
}
This won't work as you (probably expect) - if line.Equals("MODE") then line.Remove(0, "MODE".Length) is an empty string. What you probably want is line.StartsWith(cmpStr) or line.Contains(cmpStr).
GlobalData is a static class
This doesn't seem a good approach for what you're doing. You may want to read up on static classes and when to use them (MSDN is a good starting point, though it obviously can't cover everything: http://msdn.microsoft.com/en-us/library/79b3xss3%28v=vs.80%29.aspx).
Other than that, you can probably simply replace all your int fields with a dictionary (though please rethink the static approach as described above):
public static Dictionary<String, int> Items = new Dictionary<String, int>();
Then your parsing code could look like this:
while ((line = reader.ReadLine()) != null) //returns null if end of stream
{
var matchingString
= GlobalData.GlobalKeywords.FirstOrDefault(s => line.StartsWith(s));
if (matchingString != null)
GlobalData[matchingString]
= Convert.ToInt32(line.Remove(0, matchingString.Length));
}
You will then be able to fetch that data using e.g. GlobalData.Items["MODE"].
One last bit: you may consider introducing constant values in your global data class, e.g.:
public const String MODE = "MODE";
Then you can use GlobalData.Items[GlobalData.MODE] and avoid typos: writing GlobalData.Items[GlobalData.MODe] would cause a compile error.
Replace this:
public static int mode;
public static int time_year;
public static int time_month;
public static int time_day;
public static int time_hour;
public static int time_minute;
With this:
public static Dictionary<string, int> my_values = new Dictionary<string, int>();
Then replace:
GlobalData[i] = Convert.ToInt32(line.Remove(0, cmpStr.Length));
with:
GlobalData.my_values[cmpStr] = Convert.ToInt32(line.Remove(0, cmpStr.Length));
That should do what you want even though I don't understand how you expect the Convert.ToInt32 to work. The way you are calling Remove will create an empty string (which might convert to 0, I can't remember) and even if it didn't, the line doesn't contain a number because you compared it successfully to a string like "MODE".
An elegant way to solve your problem is to prepare a different action for each of the acceptable strings. You use a Dictionary(Of String, <Action>) where Action is a common delegate type that receive a string in input and know how to process it accordingly to the keyword present at the beginning of the line.
// The common signature for every methods stored in the value part of the dictionary
public delegate void ParseLine(string line);
// Global dictionary where you store the strings as keyword
// and the ParseLine as the delegate to execute
Dictionary<String, ParseLine> m_Actions = new Dictionary<String, ParseLine>() ;
void Main()
{
// Initialize the dictionary with the delegate corresponding to the strings keys
m_Actions.Add("MODE", new ParseLine(Task1));
m_Actions.Add("TIME_YEAR", new ParseLine(Task2));
m_Actions.Add("TIME_MONTH", new ParseLine(Task3));
m_Actions.Add("TIME_DAY", new ParseLine(Task4));
.....
while ((line = reader.ReadLine()) != null)
{
// Search the space that divide the keyword from the value on the same line
string command = line.Substring(0, line.IndexOf(' ')).Trim();
// a bit of error checking here is required
if(m_Actions.ContainsKey(command))
m_Actions[command](line);
}
}
void Task1(string line)
{
// this will handle the MODE line
GlobalData.Mode = Convert.ToInt32(line.Substring(line.IndexOf(' ')+1).Trim());
}
void Task2(string line)
{
GlobalData.time_year = Convert.ToInt32(line.Substring(line.IndexOf(' ')+1).Trim());
}
void Task3(string line)
{
.....
}
void Task4(string line)
{
.....
}
A simple (and not really clean) approach is to add an indexer to your global data class and decide which field to set based on the index. But you have to extend the indexer every time you add a field (basically you move the if/switch from the while Loop into the indexer).
You could also use reflection, if you can match the keyword to the field name. This is not very performant but does not need to be extended as long as you can map the keyword to the new field name.
Another approach is to create a dictionary>. In this dictionary you register the keywords, e.g. (pseudo-code):
Class Level variable:
private keywordsDict = new Dictionary<string, Action<int>>();
In a constructor:
keywordsDict.Add("MODE", delegate(value) GlobalData.mode = value);
In while-loop:
var action = keywordsDict[line];
action(value);
In the later approach, you only need to extend the dictionary but not the algorithm as such if you have a new keyword/field.
May be i can tell you how to achieve it (GlobalData[i]) in C# thought its not the answer you are looking for.
class GlobalData
{
private string[] array = new string[10];
public GlobalData()
{
//now initialize array
array[0] = "SomeThingA";
array[1] = "SomeThingB";//continue initialization.
}
public string this[int index]
{
get {return array[index];}
}
}
Now the clients can use GlobalData like ,
GlobalData gd = new GlobalData();
gd[1] = "SomeOtherThing" ; //set the value.
string value = gd[1];//get the value
But this cant be done by making the class static as you see it works with 'this'
I have a public property in a static class which I want to use as a means of validating the user's input against illegal file and path names.
At present I have this:
private static string invalidFileNames = new string(Path.GetInvalidFileNameChars());
private static string invalidPathNames = new string(Path.GetInvalidPathChars());
private static string invalidUserInput = (invalidFileNames + invalidPathNames);
public static string InvalidUserInput
{
get { return invalidUserInput; }
}
Based on Microsoft's documentation here I am expecting to get back "<>|"<>| But all I am getting is the first "<>|.
Can anyone shed some light as to what is happening here? How do I ensure that I get both strings returned?
You could just make it a single string
using System.Linq;
public static string InvalidUserInput
{
get
{
return new string(Path.GetInvalidFileNameChars()
.Concat(Path.GetInvalidPathChars())
.Distinct()
.ToArray());
}
}
You wont see them all in a TextBox because of the terminator type chars in InvalidUserInput in your case its the \0(null teminator) that its stopping the display.
if you want to display just the ones that make sense to the user you can strip out the ones that cause the issue using Char.IsControl
Here is a static class to wrap it all up
public static class StringExtentions
{
private static string _invalidUserInput = string.Empty;
private static string _PrinatbleInvalidUserInput = string.Empty;
public static string InvalidUserInput
{
get
{
if (_invalidUserInput == string.Empty)
{
_invalidUserInput = new string(Path.GetInvalidFileNameChars()
.Concat(Path.GetInvalidPathChars())
.Distinct()
.ToArray());
}
return _invalidUserInput;
}
}
public static string GetPrinatbleInvalidUserInput
{
get
{
if (_PrinatbleInvalidUserInput == string.Empty)
{
_PrinatbleInvalidUserInput = new string(InvalidUserInput.Where(x => !char.IsControl(x)).ToArray());
}
return _PrinatbleInvalidUserInput;
}
}
public static bool IsValidUserInput(this string str)
{
return !str.Any(c => InvalidUserInput.Contains(c));
}
}
usage:
public MainWindow()
{
InitializeComponent();
string myString = "C:\\InvalidPath<";
if (!myString.IsValidUserInput())
{
MessageBox.Show(string.Format("String cannot contain {0}", StringExtentions.GetPrinatbleInvalidUserInput));
}
}
You can't see them in debugger, but you can output them to the file and see them with some better editor than notepad, like notepad++
File.WriteAllText("tmp.txt", invalidUserInput, UTF8Encoding.GetEncoding("UTF-8"));
In this case, because you are presumably going to be using the data numerous times, you would want to only record characters that are unique between both character arrays. To do this, you can use the Union method as follows:
private static string invalidUserInput = new string(Path.GetInvalidFileNameChars().Union(Path.GetInvalidPathChars()).ToArray());
Running your code gives a number of unicode characters returned:
"<>|□□□□□□□□□
□□
□□□□□□□□□□□□□□□□□□:*?\/"<>|□□□□□□□□□
□□
□□□□□□□□□□□□□□□□□□
Are you sure that you are not loosing anything due to the unicode characters?
Also, is that your exact code? If you switch the ordering of the variable initalizations it will no longer work as invalidUserInput must be evaluated after the other two (they are evaluated in the order they are defined in the code).
I have a HashSet<string> that is added to periodically. What I want to do is to cast the entire HashSet to a string without doing a foreach loop. Does anybody have an example?
You will loop over the contents, whether you explicitly write one or not.
However, to do it without the explicit writing, and if by "cast" you mean "concatenate", you would write something like this
string output = string.Join("", yourSet); // .NET 4.0
string output = string.Join("", yourSet.ToArray()); // .NET 3.5
If you want a single string that is a concatenation of the values in the HashSet, this should work...
class Program
{
static void Main(string[] args)
{
var set = new HashSet<string>();
set.Add("one");
set.Add("two");
set.Add("three");
var count = string.Join(", ", set);
Console.WriteLine(count);
Console.ReadKey();
}
}
If you want a single method to get all the hashset's items concatenated, you can create a extension method.
[]'s
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
HashSet<string> hashset = new HashSet<string>();
hashset.Add("AAA");
hashset.Add("BBB");
hashset.Add("CCC");
Assert.AreEqual<string>("AAABBBCCC", hashset.AllToString());
}
}
public static class HashSetExtensions
{
public static string AllToString(this HashSet<string> hashset)
{
lock (hashset)
{
StringBuilder sb = new StringBuilder();
foreach (var item in hashset)
sb.Append(item);
return sb.ToString();
}
}
}
You can use Linq:
hashSet.Aggregate((a,b)=>a+" "+b)
which inserts a white space between two elements of your hashset