This question already has answers here:
Most efficient way to concatenate strings?
(18 answers)
Closed last month.
I have following code snippet that will append filename with current timestamp and it's working fine.
Just want to make sure this is best way to append strings in c# 10, if not then how we can we make below code more efficient?
ex: testfile.txt ->o/p testfile_timestamp.txt
string[] strName = formFile.FileName.Split('.');
string updatedFilename = strName[0] + "_"
+ DateTime.Now.ToUniversalTime().ToString("THHmmssfff") + "." +
strName[strName.Length - 1];
How about this:
// Just a stupid method name for demo, you'll find a better one :)
public static string DatifyFileName(string fileName)
{
// Use well tested Framework method to get filename without extension
var nameWithoutExtension = System.IO.Path.GetFileNameWithoutExtension(fileName);
// Use well tested Framework method to get extension
var extension = System.IO.Path.GetExtension(fileName);
// interpolate to get the desired output.
return $"{nameWithoutExtension}_{DateTime.Now.ToUniversalTime().ToString("THHmmssfff")}{extension}";
}
Or if you are familiar with Span<char>:
public static string DatifyFileName(ReadOnlySpan<char> fileName)
{
var lastDotIndex = fileName.LastIndexOf('.');
//Maybe : if( lastDotIndex < 0 ) throw ArgumentException("no extension found");
var nameWithoutExtension = fileName[..lastDotIndex];
var extension = fileName[lastDotIndex..];
return $"{nameWithoutExtension}_{DateTime.Now.ToUniversalTime().ToString("THHmmssfff")}{extension}";
}
Fiddle
And just to give some fire to the discussion :D ...
BenchmarkDotNet=v0.13.3, OS=Windows 10 (10.0.19044.2364/21H2/November2021Update)
Intel Core i9-10885H CPU 2.40GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK=7.0.101
[Host] : .NET 7.0.1 (7.0.122.56804), X64 RyuJIT AVX2
DefaultJob : .NET 7.0.1 (7.0.122.56804), X64 RyuJIT AVX2
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|----------------- |-----------:|---------:|---------:|------:|--------:|-------:|----------:|------------:|
| Interpolated | 906.7 ns | 16.92 ns | 16.61 ns | 1.08 | 0.02 | 0.0458 | 384 B | 1.66 |
| InterpolatedSpan | 842.0 ns | 13.06 ns | 12.22 ns | 1.00 | 0.00 | 0.0277 | 232 B | 1.00 |
| StringBuilder | 1,010.8 ns | 6.70 ns | 5.94 ns | 1.20 | 0.02 | 0.1068 | 904 B | 3.90 |
| Original | 960.0 ns | 18.68 ns | 19.19 ns | 1.14 | 0.03 | 0.0734 | 616 B | 2.66 |
// * Hints *
Outliers
Benchmark.StringBuilder: Default -> 1 outlier was removed (1.03 us)
Benchmark.Original: Default -> 2 outliers were removed (1.03 us, 1.06 us)
// * Legends *
Mean : Arithmetic mean of all measurements
Error : Half of 99.9% confidence interval
StdDev : Standard deviation of all measurements
Ratio : Mean of the ratio distribution ([Current]/[Baseline])
RatioSD : Standard deviation of the ratio distribution ([Current]/[Baseline])
Gen0 : GC Generation 0 collects per 1000 operations
Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
Alloc Ratio : Allocated memory ratio distribution ([Current]/[Baseline])
1 ns : 1 Nanosecond (0.000000001 sec)
Strings are immutable. whenever you modify any string, it is recreated in the memory. Hence to overcome this you should always use StringBuilder. Sample code shown below:
string[] strName = formFile.FileName.Split('.');
StringBuilder updatedFilename = new StringBuilder();
updatedFilename.Append(strName[0]);
updatedFilename.Append("_");
updatedFilename.Append(DateTime.Now.ToUniversalTime().ToString("THHmmssfff"));
updatedFilename.Append(".");
updatedFilename.Append(strName[strName.Length - 1]);
// You can get this using `ToString()` method
string filename = updatedFilename.ToString();
I'm extracting data from an API which gives me some information in JSON. However, one of the values gives me:
{X0=80,X1=80,X2=80,X3=80,X4=80,X5=80,X6=80,X7=80,X8=80,X9=80,X10=80,X11=80,X12=80,X13=80,X14=80,X15=80,X16=80,X17=80,X18=80,X19=80,X20=80,X21=80,X22=80,X23=80,X24=80,X25=80,X26=80,X27=80,X28=80,X29=80,X30=80,X31=80,X32=80,X33=80,X34=80,X35=80,X36=80,X37=80,X38=80,X39=80,X40=80,X41=80,X42=80,X43=80,X44=80,X45=80,X46=80,X47=80,X48=80,X49=80,X50=80,X51=80,X52=80,X53=80,X54=80,X55=80,X56=80,X57=80,X58=80,X59=80,X60=80,X61=80,X62=80}
I am trying to decode this for use in a C# Console Application (.NET).
My main question is, what would be the best way to extract this string into a dictionary or array? I am not sure if I have worded this correctly, so please correct me if I'm wrong!
Thanks!
You can use Linq with Trim, Split, Select, ToDictionary
var result = json.Trim('{', '}')
.Split(',')
.Select(x => x.Split('='))
.ToDictionary(x => x[0], x => int.Parse(x[1]));
Console.WriteLine(string.Join("\r\n", result.Select(x => x.Key + " : " + x.Value)));
Full Demo Here
And just because i'm bored
Benchmarks
Mode : Release (64Bit)
Test Framework : .NET Framework 4.7.1
Operating System : Microsoft Windows 10 Pro
Version : 10.0.17134
CPU Name : Intel(R) Core(TM) i7-3770K CPU # 3.50GHz
Description : Intel64 Family 6 Model 58 Stepping 9
Cores (Threads) : 4 (8) : Architecture : x64
Clock Speed : 3901 MHz : Bus Speed : 100 MHz
L2Cache : 1 MB : L3Cache : 8 MB
Benchmarks Runs : Inputs (1) * Scales (3) * Benchmarks (3) * Runs (100) = 900
Results
--- Random Set ----------------------------------------------------------------------
| Value | Average | Fastest | Cycles | Garbage | Test | Gain |
--- Scale 100 -------------------------------------------------------- Time 0.229 ---
| Split | 0.058 ms | 0.043 ms | 207,064 | 48.000 KB | Base | 0.00 % |
| JsonReplace | 0.077 ms | 0.064 ms | 273,556 | 24.000 KB | Pass | -32.38 % |
| Regex | 0.270 ms | 0.235 ms | 950,504 | 80.000 KB | Pass | -364.87 % |
--- Scale 1,000 ------------------------------------------------------ Time 0.633 ---
| Split | 0.490 ms | 0.446 ms | 1,718,180 | 495.102 KB | Base | 0.00 % |
| JsonReplace | 0.671 ms | 0.596 ms | 2,352,043 | 195.078 KB | Pass | -36.86 % |
| Regex | 2.544 ms | 2.293 ms | 8,897,994 | 731.125 KB | Pass | -419.00 % |
--- Scale 10,000 ----------------------------------------------------- Time 5.005 ---
| Split | 5.247 ms | 4.673 ms | 18,363,748 | 4.843 MB | Base | 0.00 % |
| JsonReplace | 6.782 ms | 5.488 ms | 23,721,593 | 1.829 MB | Pass | -29.25 % |
| Regex | 31.840 ms | 27.008 ms | 111,277,134 | 6.637 MB | Pass | -506.80 % |
-------------------------------------------------------------------------------------
Data
private string GenerateData(int scale)
{
var ary = Enumerable.Range(0, scale)
.Select(x => $"X{x}={Rand.Next()}")
.ToList();
return $"{{{string.Join(",", ary)}}}";
}
Split
public class Split : Benchmark<string, Dictionary<string,int>>
{
protected override Dictionary<string,int> InternalRun()
{
return Input.Trim('{', '}')
.Split(',')
.Select(x => x.Split('='))
.ToDictionary(x => x[0], x => int.Parse(x[1]));
}
}
Regex
Credited to emsimpson92 using Cast
public class Regex : Benchmark<string, Dictionary<string,int>>
{
protected override Dictionary<string,int> InternalRun()
{
var regex = new System.Text.RegularExpressions.Regex("(?<key>[^,]+)=(?<value>[^,]+)");
var matchCollection = regex.Matches(Input.Trim('{', '}'));
return matchCollection.Cast<Match>()
.ToDictionary(
x => x.Groups["key"].Value,
x => int.Parse(x.Groups["value"].Value));
}
}
JsonReplace
Credited to Hanzalah Adalan Modified to work with string.replace
public unsafe class JsonReplace : Benchmark<string, Dictionary<string,int>>
{
protected override Dictionary<string,int> InternalRun()
{
return JsonConvert.DeserializeObject<Dictionary<string,int>>(Input.Replace("=", ":"));
}
}
Additional Resources
String.Trim Method
Returns a new string in which all leading and trailing occurrences of
a set of specified characters from the current String object are
removed.
String.Split Method (String[], StringSplitOptions)
Splits a string into substrings based on the strings in an array. You
can specify whether the substrings include empty array elements.
Enumerable.Select Method (IEnumerable, Func)
Projects each element of a sequence into a new form.
Enumerable.ToDictionary Method (IEnumerable, Func)
Creates a Dictionary from an IEnumerable according to
a specified key selector function.
This can be done with Regex The following pattern will capture keys and values in 2 separate groups (?<key>[^,]+)=(?<value>[^,]+)
Demo
Once you have your MatchCollection, run a foreach loop through it, and add each element to a dictionary.
Var myAwesomeDictionary = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string,string>>(_yourJsonStringHere);
Need help to find the regular expression.
Text = #"{'the quick' | 'the lazy'}{{{'BEFORE'} 'fox' } | {{'BEFORE'} 'lion'}}"
Result String Array Should be -
[0] = 'the quick' | 'the lazy',
[1] = BEFORE/1 fox | BEFORE/2 lion
Unless two or more strings are split by |, I need them to side by side.
thanks for your help. After thinking a bit, I was able to find the simple solution.
string sampleText = "{'what is' | 'when is'}{BEFORE/2 }{doing | 'to do'}{ NEAR/3 }{'to ensure our' | 'to ensure that' | 'to make sure our' | 'to make sure that our' | 'so our' | 'so that our'}{ BEFORE/4 }{doesn*t | 'does not' | 'will not' | won*t}";
List<List<string>> list = new List<List<string>>();
Match mt = Regex.Match(sampleText, #"\}([^{]*)\{");
string value = sampleText.Replace(mt.Value, "},{");
string[] newArray = value.Split(",".ToCharArray());
foreach (string st in newArray)
{
list.Add(new List<string>(st.Replace("{", "").Replace("}", "").Split('|')));
}
I am learning validation expressions and have attempted to write one to check a decimal like the example below but I am having some issues.
The number to validate is like this:
00.00 (any 2 numbers, then a ., then any 2 numbers)
This is what I have:
^[0-9]{2}[.][0-9]{2}$
This expression returns false but from a tutorial I read I was under the understanding that it should be written like this:
^ = starting character
[0-9] = any number 0-9
{2} = 2 numbers 0-9
[.] = full stop
$ = end
Use the right tool for the job. If you're parsing decimals, use decimal.TryParse instead of Regex.
string input = "00.00";
decimal d;
var parsed = Decimal.TryParse(input, out d);
If the requirement is to always have a 2 digits then a decimal point then 2 digits you could do:
var lessThan100 = d < 100m;
var twoDecimals = d % 0.01m == 0;
var allOkay = parsed && lessThan100 && twoDecimals;
So our results are
Stage | input = "" | "abc" | "00.00" | "123" | "0.1234"
-------------------------------------------------------------
parsed | false | false | true | true | true
lessThan100 | - | - | true | false | true
twoDecimals | - | - | true | - | false
Although if you really need it to be that exact format then you could do
var separator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
var allOkay = isOkay && input.Length == 5 && input[2] == separator;
If you absolutely have to use Regex then the following works as required:
Regex.IsMatch("12.34", #"^([0-9]{2}\.[0-9]{2})$")
Regex explanation:
^ - start of string
() - match what's inside of brackets
[0-9]{2} exactly 2 characters in the range 0 - 9
\. - full stop (escaped)
$ - end of string
I've got a SQL Reporting Services 2005 report that includes a textbox on the first page. The string input for the textbox can be very large and include newlines. The textbox size is fixed and another textbox is included on the second page of the report to handle any spillover text that didn't fit in the first page's textbox. If the second page textbox becomes full, then I would like to include a "..." at the end of the text to show that some text was cut off.
I've tried using the TextRenderer.MeasureText() method but it appears to only work on single lines. I'm trying the following code
string str = string.Copy(commentString);
TextFormatFlags flags = TextFormatFlags.WordBreak |
TextFormatFlags.WordEllipsis |
TextFormatFlags.ModifyString;
float textBoxWidthInches = 3.8f;
float textBoxHeightInches = 3.4f;
Size size = new Size(
(int)(textBoxWidthInches * 72 - 2),
(int)(textBoxHeightInches * 72 - 2));
TextRenderer.MeasureText(str, new Font("Arial", 8), size, flags);
I then expect the str to include a '\0' at the point where I need to break my string, however, it isn't showing up. If I remove the WordBreak flag and input a string with a long first line, it does include the '\0' at the correct spot for the first line, but it is only working for a single line.
My questions are:
1) How do I "continue" text from one textbox to another in Sql Reporting Services 2005?
2) If not, how can I calculate where I need to break my string up so that it fits inside a textbox?
3) Optionally I'd like to include a "..." at the end of the second textbox for text that is longer than both textboxes can fit.
Edit:
What I'm trying to accomplish is something like this:
|------------------------|
| |
| Header Page 1 |
|------------------------|
| | |
| | |
|TextBox1| |
| | |
|--------| |
| |
| Other Data |
| |
| |
| |
| |
| |
| |
| |
|------------------------|
|------------------------|
| |
| Header Page 2 |
|------------------------|
| | |
| | |
|TextBox2| |
| | |
|--------| |
| |
| Other Data |
| |
| |
| |
| |
| |
| |
| |
|------------------------|
I then want TextBox1 to continue to TextBox2 on the second page. Using the CanGrow attribute will not get the desired behavior.
Here is what I ended up with:
public static IList<string> WordWrap(
string text,
Font printFont,
Graphics graphics,
IList<SizeF> sizeF,
string tooLongText)
{
List<string> list = new List<string>();
int charsFit;
int linesFilled;
foreach (SizeF size in sizeF)
{
graphics.MeasureString(
text,
printFont,
size,
new StringFormat(),
out charsFit,
out linesFilled);
char[] whitespace = new[] { ' ', '\t', '\r', '\n' };
int index = charsFit;
if (text.Length > charsFit)
index = text.LastIndexOfAny(whitespace, charsFit);
if (index < 0) index = charsFit;
string rv = text.Substring(0, index).Trim();
text = text.Substring(index).Trim();
list.Add(rv);
}
if (!string.IsNullOrEmpty(text))
{
string lastText = list[list.Count - 1];
SizeF size = sizeF[sizeF.Count - 1];
charsFit = 0;
string newLastText = lastText + Environment.NewLine + tooLongText;
while (charsFit < newLastText.Length)
{
graphics.MeasureString(
newLastText,
printFont,
size,
new StringFormat(),
out charsFit,
out linesFilled);
lastText = lastText.Substring(0, lastText.Length - 1);
newLastText = lastText + Environment.NewLine + tooLongText;
}
list.RemoveAt(list.Count - 1);
list.Add(newLastText);
}
return list;
}
I then generate two public string properties for my two textboxes. If this function returns a list with count > 0, first textbox value = list[0]. If list count > 1, second textbox value = list[1]. Not the best code I'm sure, but it worked for my needs.
When CanGrow is set the text box will split over more than one page once the height is larger than a page. eg. The textbox will move to a second page first if it shares a page with another control. See here.
If you have to cut the textbox off at two pages you could set the textbox size to 2 pages, then set CanGrow to false and CanShrink to true. This way the textbox would always be smaller than 2 pages.
Then test the number of lines in your text (You may have to write a function to do this) versus the lines you know can fit in in the textbox to control the visibility of another textbox containing the message "Further text truncated".
You can also approximate this behaviour by using a table, groupings and breaking after each group. So page 1 would be the first value of group1 and page 2 would be the second value of group1, etc.
The detail group would be the content of the page (or you could add more groupings if you like)