What is the C# equivalent of this pseudo-code?
var pattern = ...;
var lookup = new Dictionary<string, string>();
foreach (var group in new Regex(pattern).Matches())
{
lookup[group.Name] = group.Value;
}
I don't see any System.Text.RegularExpressions group-related object that exposes the group name.
What am I missing?
What I'm actually trying to do is convert a file with lines in this format:
eventName|message|date
To an IEnumerable<EventLogLine>, with EventLogLine being:
public struct EventLogLine
{
public string EventName { get; set; }
public string Message { get; set; }
public DateTime Date { get; set; }
}
And put those lines into a IDictionary<string /*EventName*/, IEnumerable<EventLogLine>>.
I just knocked this up in using LINQ. It relies on the List<string> to be filled with the lines in the file.
var lines = new List<string>();
var dict = lines.Select(l =>
{
var sp = l.Split('|');
return new EventLogLine { EventName = sp[0], Message = sp[1], Date = DateTime.Parse(sp[2]) };
})
.GroupBy(e => e.EventName)
.ToDictionary(grp => grp.Key, grp => grp.AsEnumerable());
Basically you convert each line to an EventLogLine, using the Select(), then use the GroupBy() to create your grouping based on EventName, then using the ToDictionary() to run the query and create your dictionary in the format required!
See the example in the Match.Groups MSDN article. I think you should look at Alastair's answer though, seeing as your input is so simple it would probably be easier to read the code later if you just use ReadLine and Split.
Consider using ToLookup rather than ToDictionary. Lookups work naturally with linq and generic code in general by being immutable and by exposing a a very simple API. Also, I would encapsulate the parsing into the EventLogLine struct.
As a result, the code would look like this:
IEnumerable<string> lines;
ILookup<string, EventLogLine> lookup =
lines.Select(EventLogLine.Parse).ToLookup(evtLine => evtLine.EventName);
An example consumer:
if(lookup["HorribleEvent"].Any())
Console.WriteLine("OMG, Horrible!");
foreach(var evt in lookup["FixableEvent"])
FixIt(evt);
var q = from evtName in relevantEventNames
from evt in lookup[evtName]
select MyProjection(evt);
Note that you do not need to check for key-existance, unlike for a Dictionary:
if(dictionary.ContainsKey("HorribleEvent")) //&& dictionary["HorribleEvent"].Any() sometimes needed
Console.WriteLine("OMG, Horrible!");
if(dictionary.ContainsKey("FixableEvent"))
foreach(var evt in lookup["FixableEvent"])
FixIt(evt);
var q = from evtName in relevantEventNames.Where(dictionary.ContainsKey)
from evt in dictionary[evtName]
select MyProjection(evt);
As you may notice, working with a dictionary containing IEnumerable values introduces subtle friction - ILookup is what you want!
Finally, the modified EventLogLine:
public struct EventLogLine {
public string EventName { get; private set; }
public string Message { get; private set; }
public DateTime Date { get; private set; }
public static EventLogLine Parse(string line) {
var splitline = line.Split('|');
if(splitline.Length != 3) throw new ArgumentException("Invalid event log line");
return new EventLogLine {
EventName = splitline[0],
Message = splitline[1],
Date = DateTime.Parse(splitline[2]),
};
}
}
To answer this part of your question:
I don't see any
System.Text.RegularExpressions
group-related object that exposes the
group name. What am I missing?
I have adapted Eamon Nerbonne's struct to use regular expressions:
public struct EventLogLine
{
public string EventName { get; private set; }
public string Message { get; private set; }
public DateTime Date { get; private set; }
private static Regex expectedLineFormat = new Regex(
#"^(?<eventName>[^|]*)\|(?<message>[^|]*)\|(?<date>[^|]*)$",
RegexOptions.Singleline | RegexOptions.Compiled
);
public static EventLogLine Parse(string line) {
Match match = expectedLineFormat.Match(line);
if (match.Success) {
return new EventLogLine {
EventName = match.Groups["eventName"].ToString(),
Message = match.Groups["message"].ToString(),
Date = DateTime.Parse(match.Groups["date"].ToString()
};
}
else {
throw new ArgumentException("Invalid event log line");
}
}
}
To more directly answer your original question (without commenting on your approach), as I had a similar problem...
According to Mono source code, the enumeration for the Groups indexer is based on the private Match.regex field, so you'll need to still have the Regex. But if you do, like you had above...
public static Dictionary<string, string> ToDictionary(
Regex regex, GroupCollection groups)
{
var groupDict = new Dictionary<string, string>();
foreach (string name in regex.GetGroupNames()){ //the only way to get the names
Group namedGroup = groups[name]; //test for existence
if (namedGroup.Success)
groupDict.Add(name, namedGroup.Value);
}
return groupDict;
}
or, as Linq,
regex.GetGroupNames()
.Where(name => groups[name].Success)
.ToDictionary(name => name, name => groups[name].Value)
Related
I am trying to get the object value but I don't know how to do it. I'm new to C# and its giving me syntax error. I want to print it separately via the method "PrintSample" How can I just concatenate or append the whatData variable . Thank you.
PrintSample(getData, "name");
PrintSample(getData, "phone");
PrintSample(getData, "address");
//Reading the CSV file and put it in the object
string[] lines = File.ReadAllLines("sampleData.csv");
var list = new List<Sample>();
foreach (var line in lines)
{
var values = line.Split(',');
var sampleData = new Sample()
{
name = values[0],
phone = values[1],
address = values[2]
};
list.Add(sampleData);
}
public class Sample
{
public string name { get; set; }
public string phone { get; set; }
public string adress { get; set; }
}
//Method to call to print the Data
private static void PrintSample(Sample getData, string whatData)
{
//THis is where I'm having error, how can I just append the whatData to the x.?
Console.WriteLine( $"{getData. + whatData}");
}
In C# it's not possible to dynamically evaluate expression like
$"{getData. + whatData}"
As opposed to languages like JavaScript.
I'd suggest to use rather switch expression or Dictionary<string, string>
public void PrintData(Sample sample, string whatData)
{
var data = whatData switch
{
"name" => sample.name,
"phone" => sample.phone,
"address" => sample.address
_ => throw new ArgumentOutOfRangeException(nameof(whatData)),
};
Console.WriteLine(data);
}
I'm not sure what you are trying to achieve. Perhaps this will help you:
private static void PrintSample(Sample getData, string whatData)
{
var property = getData.GetType().GetProperty(whatData);
string value = (string)property?.GetValue(getData) ?? "";
Console.WriteLine($"{value}");
}
What PO really needs is
private static void PrintSamples(List<Sample> samples)
{
foreach (var sample in samples)
Console.WriteLine($"name : {sample.name} phone: {sample.phone} address: {sample.address} ");
}
and code
var list = new List<Sample>();
foreach (var line in lines)
{
......
}
PrintSamples(list);
it is radicolous to use
PrintSample(getData, "name");
instead of just
PrintSample(getData.name)
You can do this using reflection. However, it's known to be relatively slow.
public static void PrintSample(object getData, string whatData)
{
Console.WriteLine( $"{getData.GetType().GetProperty(whatData).GetValue(getData, null)}");
}
I want to replace words in a string that matches a keywords stored in an array with data from matching column in a table.
My model is People
public class People()
{
public int Id { get; set; }
public string Title { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
The method i have tried is :
public void ProcessString(string message)
{
using (DBEntities db = new DBEntities())
{
var people = db.People.ToList();
foreach(var person in people)
{
string[] keyword = {"#Title", "#Name", "#Surname"};
for (int i=0; i<keyword.Length; i++)
{
string updatedString = Regex.Replace(body, keyword[i], matchingcolumndata);
}
}
}
So instead of matchingcolumndata in the string updatedString = Regex.Replace(body, keyword[i], matchingcolumndata); line i want to put the data that is matching the column in my table People. Please help with the question if its not straight to the point.
From my understand it may be you are looking for
foreach(var person in people)
{
string[] keyword = {"#Title", "#Name", "#Surname"};
body= body.Replace(keyword[0],person.Title);
body= body.Replace(keyword[1],person.Name);
body= body.Replace(keyword[2],person.Surname);
}
It doesn't make any sense to declare your keywords array inside the loop. If you want to do this in a neat way (and subject to modifications easily), you can create a map between the keyword and the property of the Person. Like this:
using (DBEntities db = new DBEntities())
{
var people = db.People.ToList();
// maps each keyword to a property in the Person class
var keywordPropertyMapping = new Dictionary<string, Func<Person, string>>()
{
{ "#Title", p => p.Title },
{ "#Name", p => p.Name },
{ "#Surname", p => p.Surname }
};
foreach (var person in people)
{
foreach(var keywordFunc in keywordPropertyMapping)
{
body = body.Replace(keywordFunc.Key, keywordFunc.Value(people));
}
}
}
For any new keyword, you just add one simple line inside the dictioanry keywordPropertyMapping, and the magic works.
If you're not familiar with Func<T, TResult> which is one critical part of the Linq magic, then read the Docs:
Encapsulates a method that has one parameter and returns a value of
the type specified by the TResult parameter.
You can use this delegate to represent a method that can be passed as
a parameter without explicitly declaring a custom delegate. The
encapsulated method must correspond to the method signature that is
defined by this delegate. This means that the encapsulated method must
have one parameter that is passed to it by value, and that it must
return a value.
Is this bad practice? Or is it completely fine to do this
private readonly List<KeyValuePair<GameType, KeyValuePair<string, string>>> _tempStats = new List<KeyValuePair<GameType, KeyValuePair<string, string>>>();
GameType is an enumerator by the way.
I have data that I am downloading from a table that has a few different GameTypes with two strings associated with them. So it will parse the data and determines what GameType to assign it, and it finds the tables Key and it's Value. And it works, it stores the information and I am able to retrieve it with no problems, but it just seems like having a list of a KeyValuePair with a KeyValuePair isn't right, but maybe it is. Would using a tuple be a better approach?
My current usage of the list
private void ParseNodeText(string nText, GameType gmode)
{
_tempStats.Clear();
var reader = new StringReader(nText);
while((nText = reader.ReadLine()) != null)
{
nText = nText.Replace(" ", "");
if (nText == "")
{
continue;
}
string statType = Regex.Replace(nText, "[^A-Za-z]", "");
string statValue = Regex.Replace(nText, "[^0-9]", "");
// Console.WriteLine(gmode + " : Found line with Type of {0} and a value of {1}",statType,statValue);
_tempStats.Add(new KeyValuePair<GameType, KeyValuePair<string, string>>(gmode, new KeyValuePair<string, string>(statType, statValue)));
}
}
Your Solution is fine, but not that good readable.
You could do something like this:
private readonly List<GameStats> _tempStats = new List<GameStats>();
and GameStats is a own written simple Class:
public class GameStats
{
public GameType Type { get; set; }
public string StatType { get; set; }
public string StatValue { get; set; }
public GameStats(GameType gameType, string statType, string statValue)
{
this.Type = gameType;
this.StatType = statType;
this.StatValue = statValue;
}
}
and add it to your list like:
_tempStats.Add(new GameStats(gmode, statType, statValue));
i think, this looks good and will do it.
I want to use a foreach k, v pairs loop to run through a multidimensional list, and output those values elsewhere. Here is the code:
public class LogTable
{
public string FunctionName { get; set; }
public string LogTime { get; set; }
}
public class TableVars
{
public List<LogTable> loggingTable { get; set; }
public TableVars()
{
loggingTable = new List<LogTable>();
}
public void createLogList()
{
loggingTable = new List<LogTable>();
}
}
foreach( KeyValuePair<string, string> kvp in tablevars.loggingTable)
{
// output would go here. I haven't looked it up yet but I assume it's something
// along the lines of var = k var2 = v? Please correct me if I'm wrong.
}
When I run my mouse over 'foreach' I get a warning saying - 'Cannot convert type 'DataObjects.LogTable' to 'System.Collections.Generic.KeyValuePair. How can I resolve this issue, or is there a more efficient way to accomplish the same goal?
Thanks!
I should have added more context, sorry. I'm trying to return the two different values inside the properties 'FunctionName' and 'LogTime' which I have added via:
var tablevars = new TableVars();
tablevars.loggingTable.Add(new LogTable { FunctionName = errorvalue, LogTime = logtime });
To specify more accurately, the intention of the foreach k, v loop was to grab every distinct property of FunctionName and LogTime and input them into a database in SQL Server. This is why the distinction between k, v (or FunctionName, LogTime) is important. Again, please correct me if I'm wrong.
You cannot use KeyValuePair<>, because you don't enumerate a Dictionary<>. But you don't need to, simply do this:
foreach(LogTable logTable in tablevars.loggingTable)
{
// do whatever with logTable.FunctionName and logTable.LogTime
}
Either change your foreach to iterate through List<LogTable>
foreach( LogTable lt in tablevars.loggingTable)
{...}
OR
Use a KeyValuePair instead of creating class LogTable
public class TableVars
{
public Dictionary<string,string> loggingTable { get; set; }
public TableVars()
{
loggingTable = new Dictionary<string,string>();
}
public void createLogList()
{
loggingTable = new Dictionary<string, string>();
}
}
foreach( KeyValuePair<string, string> kvp in tablevars.loggingTable)
{
//loggingTagle is now a KeyValuePair
}
I have this class:
public class allFields
{
public string EAN { get; set; }
public string title { get; set; }
public string qty { get; set; }
public string price { get; set; }
public DateTime date { get; set; }
}
And a function that return an anonymous type:
public IEnumerable<object> stockEtatQty()
{
List<allFields> afList = new List<allFields>();
var query = from x in ctx.book
where x.qty > 0
select x;
foreach (var item in query)
{
allFields af = new allFields();
af.EAN = item.EAN;
af.title = item.Titre;
af.qty = ""+item.Quantite;
afList.Add(af);
}
var q = from x in afList
select new { EAN=x.EAN, Title=x.title, Quantity=x.qty };
return q; //q is a IEnumerable<'a> where a is new {string EAN, string Title, string Quantity}
}
In my WinForm a use this function as below:
private void QuantityToolStripMenuItem_Click(object sender, EventArgs e)
{
ServiceStock sstock = new ServiceStock();
var q = sstock.stockEtatQty().ToList();// q is a list<object>
string str = "";
foreach (var item in q)
{
str += item + Environment.NewLine;
}
MessageBox.Show(str);
}
The result is:
{ EAN = 1, Title = CSharp Security, Quantity = 970 }
{ EAN = 2, Title = MISC, Quantity = 100 }
...
What I want?
I want not like the result above, but separate each field apart of the item in the loop foreach, e.g get item.EAN, item.Title and item.Quantity.
If there is no solution for my problem I would like to know an alternative,
Thanks for help.
The obvious solution is to create a custom type (let's call it BookInfo) and return a IEnumerable<BookInfo> instead of a IEnumerable<object> (and maybe override ToString if you want to put the formatting into this class itself).
Then you can easily format the output.
public class BookInfo
{
public string EAN {get;set;}
public string Title {get;set;}
public int Quantity {get;set;}
}
public IEnumerable<BookInfo> stockEtatQty()
{
...
var q = from x in afList
select new BookInfo { EAN=x.EAN, Title=x.title, Quantity=x.qty };
return q;
}
private void QuantityToolStripMenuItem_Click(object sender, EventArgs e)
{
ServiceStock sstock = new ServiceStock();
var q = sstock.stockEtatQty();
var message = string.Join(Environment.NewLine,
q.Select(item => String.Format("{0} {1} {2}", item.EAN, item.Title, item.Quantity)));
MessageBox.Show(message);
}
Since the static type information about the object of anonymous type is lost by the time that you exit stockEtatQty() method, you could cast the object to dynamic and access fields like this:
str = string.Join(Environment.NewLine, q.Cast<dynamic>().Select(item =>
string.Format("{0} {1} {2}", item.EAN, item.Title, item.Quantity)
));
The cast to dynamic tells the compiler that EAN, Title, and Quantity need to be resolved at runtime.
Note that I also replaced the foreach loop with a call to string.Join to improve performance: repeated string concatenation creates unnecessary partial string objects, which string.Join avoids. Another solution would be to use StringBuider instead of string concatenation +=.
stockEtatQty is in a project (Service) and QuantityToolStripMenuItem_Click is in another project (View)
Unfortunately, this means that you would not be able to use anonymous types: anonymous types are generated with internal visibility, limiting their use to the assembly in which they are produced. You can use a work-around based on ExpandoObject described in this answer:
var q = afList.Select(x => {
dynamic res = new ExpandoObject();
res.EAN=x.EAN;
res.Title=x.title;
res.Quantity=x.qty;
return res;
});
Create a new class that represents the new object structure and return that.
var q = from x in afList
select new SmallerType { EAN=x.EAN, Title=x.title, Quantity=x.qty };
WinForm Function
foreach (SmallerType item in q)
{
//
}
You can use collection of dynamic objects instead of simple objects as return type of your method:
public IEnumerable<dynamic> stockEtatQty()
Then you will not have IntelliSense but at runtime properties will be found:
foreach (var item in sstock.stockEtatQty())
str += String.Format("{0}", item.EAN) + Environment.NewLine;
But I suggest you to create custom class with EAN, Title and Quantity properties. Or just use your allFields instead of anonymous objects.
Consider also to use StringBuilder for string creation to avoid creating lot of in-memory strings:
var builder = new StringBuilder();
foreach (var item in sstock.stockEtatQty())
builder.AppendFormat("{0}{1}", item.EAN, Environment.NewLine);
MessageBox.Show(builder.ToString());