I really don't think this is a good idea, and Reflection can be terribly slow, but here we go.
First, we need some extensions to make dealing with properties and fields a little cleaner:
public static class HelperExtensions {
// ***
// *** Type Extensions
// ***
public static List<MemberInfo> GetPropertiesOrFields(this Type t, BindingFlags bf = BindingFlags.Public | BindingFlags.Instance) =>
t.GetMembers(bf).Where(mi => mi.MemberType == MemberTypes.Field | mi.MemberType == MemberTypes.Property).ToList();
// ***
// *** MemberInfo Extensions
// ***
public static void SetValue<T>(this MemberInfo member, object destObject, T value) {
switch (member) {
case FieldInfo mfi:
mfi.SetValue(destObject, value);
break;
case PropertyInfo mpi:
mpi.SetValue(destObject, value);
break;
default:
throw new ArgumentException("MemberInfo must be of type FieldInfo or PropertyInfo", nameof(member));
}
}
public static TOut Apply<TIn, TOut>(this TIn m, Func<TIn, TOut> applyFn) => applyFn(m);
}
Then, we need to create a class to represent the desired result:
public class ParsedMessage {
public string ID;
public string Level;
public string Message;
}
Now, we write an extension to map Group
named values to properties or fields in an object:
public static class MatchExt {
public static T MakeObjectFromGroups<T>(this Match m) where T : new() {
var members = typeof(T).GetPropertiesOrFields().ToDictionary(pf => pf.Name.ToLower());
var ans = new T();
foreach (Group g in m.Groups) {
if (members.TryGetValue(g.Name.ToLower(), out var mi))
mi.SetValue(ans, g.Value);
}
return ans;
}
public static string[] MakeArrayFromGroupValues(this Match m) {
var ans = new string[m.Groups.Count-1];
for (int j1 = 1; j1 < m.Groups.Count; ++j1)
ans[j1-1] = m.Groups[j1].Value;
return ans;
}
}
Finally, we can use our new extension:
var parsed = Regex
.Match("123: qwe: qweasd", @"(?<id>\d+?): (?<level>\w+?): (?<message>\w+?)")
.Apply(m => m.Success ? m.MakeObjectFromGroups<ParsedMessage>() : null);
Note: it is possible to create anonymous types on the fly at runtime, but they are rarely useful. Since you don't know anywhere else in your code what the properties are, you must do everything through Reflection, and unless you are using the objects in a Reflection heavy environment like ASP.Net, you might as well use a Dictionary
, or if you must, a DynamicObject
(though, again, without knowing the field names that isn't too practical).
I added an additional extension to map Group
s to a string[]
. Since names for ValueTuple
fields are only usable at compile time, creating an array and using indexes is just as good as creating a ValueTuple
and using Item1
, etc.
Finally, an attempt to work with an anonymous object. By passing in a template for the anonymous object, you can create a new anonymous object from the capture group values that have matching names.
Using a method extension for type inference:
public static class ToAnonymousExt {
public static T ToAnonymous<T>(this T patternV, Match m) {
var it = typeof(T).GetPropertiesOrFields();
var cd = m.Groups.Cast<Group>().ToDictionary(g => g.Name, g => g.Value);
return (T)Activator.CreateInstance(typeof(T), Enumerable.Range(0, it.Count).Select(n => cd[it[n].Name]).ToArray());
}
}
Now you can pass in an anonymous type as a template and get a filled in anonymous object back. Note that only the fields in the anonymous type that match capture group names will be filled in, and no runtime error handling is done.
var parsed3 = Regex.Match("123: qwe: qweasd", @"(?<id>\d+?): (?<level>\w+?): (?<message>\w+?)")
.Apply(m => m.Success ? new { message = "", id = "", level = "" }.ToAnonymous(m) : null);