Extension-Method zur Regex-Klasse, um anhand der Gruppennamen passende Objekte zu erzeugen
Hier erstmal das Snippet. Ein Beispiel folgt weiter unten.
public static class RegexExtensions
{
public static IEnumerable<T> Matches<T>(this Regex regex, string input, ICustomConverter<T> converter = null) where T : class, new()
{
if (typeof(T) == typeof(object))
return MatchesDynamic<T>(regex, input, converter);
else
return MatchesDefault<T>(regex, input, converter);
}
private static IEnumerable<T> MatchesDefault<T>(this Regex regex, string input, ICustomConverter<T> converter = null) where T : class, new()
{
var bindingFlags = BindingFlags.IgnoreCase | BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance;
var t = typeof(T);
foreach (Match m in regex.Matches(input).OfType<Match>().Where(f => f.Success))
{
T itm = new T();
for (int i = 1; i < m.Groups.Count; ++i)
{
var groupName = regex.GroupNameFromNumber(i);
if (converter == null || !converter.Convert(groupName, m.Groups[i], itm))
{
var pi = t.GetProperty(groupName, bindingFlags);
if (pi != null)
pi.SetValue(itm, Convert.ChangeType(m.Groups[i].Value, pi.PropertyType), null);
}
}
yield return itm;
}
}
private static IEnumerable<T> MatchesDynamic<T>(this Regex regex, string input, ICustomConverter<T> converter = null) where T : class, new()
{
var groupNames = regex.GetGroupNames();
foreach (Match m in regex.Matches(input).OfType<Match>().Where(f => f.Success))
{
dynamic itm = new DynamicResult(m, groupNames);
for (int i = 1; i < m.Groups.Count; ++i)
{
var groupName = regex.GroupNameFromNumber(i);
if (converter != null)
converter.Convert(groupName, m.Groups[i], itm);
}
yield return itm;
}
}
public interface ICustomConverter<T>
{
// Liefert true, falls manuell konvertiert wurde, ansonsten false
bool Convert(string groupName, Group group, T item);
}
private sealed class DynamicResult : DynamicObject
{
private Dictionary<string, string> values;
public DynamicResult(Match match, string[] groupNames)
{
this.values = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
foreach (var grpName in groupNames)
this.values.Add(grpName, match.Groups[grpName].Value);
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
string grpValue = null;
result = this.values.TryGetValue(binder.Name, out grpValue) ? grpValue : null;
return result != null;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
this.values[binder.Name] = value.ToString();
return true;
}
}
}
Hat man beispielsweise Zeichenfolgen die die Grundlage für bestimmte Objekte darstellen, so kann man diese automatisch mappen lassen.
Das Beispiel zeigt ein Regex-Muster für Personendaten und die entsprechende Person-Klasse.
Die Namen der Regex-Gruppen müssen den Properties der Klasse entsprechen. Falls dies nicht möglich sein sollte oder man in das Mapping eingreifen möchte,
so kann ein ICustomConverter-Objekt übergeben werden.
Die Verwendung von "regexCustomConverter" zeigt dies beispielhaft.
Ausserdem gibt es noch eine dynamische Variante, indem man als generischen Parameter 'dynamic' übergibt.
static class Program
{
static void Main(string[] args)
{
var regex = new Regex(@"(?<vorname>\w+)\s+(?<nachname>\w+)\s+\((?<age>\d+)\)");
var regexCustomConverter = new Regex(@"(?<vorname>\w+)\s+(?<nachname>\w+)\s+\((?<age>\d+)\)\s+\((?<gender>[a-z])\)");
// "Normale" Verwendung
foreach (var p in regex.Matches<Person>("Max Mustermann (23)"))
{
Console.WriteLine(p.Vorname);
Console.WriteLine(p.Nachname);
Console.WriteLine(p.Age);
}
Console.WriteLine();
// Verwendung eines CustomConverters
foreach (var p in regexCustomConverter.Matches<Person>("Max Mustermann (23) (m)", new PersonConverter()))
{
Console.WriteLine(p.Vorname);
Console.WriteLine(p.Nachname);
Console.WriteLine(p.Age);
Console.WriteLine(p.Gender);
}
Console.WriteLine();
// Verwendung mittels dynamic
foreach (var p in regex.Matches<dynamic>("Max Dynamic (23)"))
{
Console.WriteLine(p.Vorname);
Console.WriteLine(p.Nachname);
Console.WriteLine(p.Age);
}
Console.WriteLine();
// Verwendung mittels dynamic (und Converter)
foreach (var p in regexCustomConverter.Matches<dynamic>("Max Dynamic (23) (f)", new DynamicConverter()))
{
Console.WriteLine(p.Vorname);
Console.WriteLine(p.Nachname);
Console.WriteLine(p.Age);
Console.WriteLine(p.Gender);
}
Console.ReadLine();
}
public class PersonConverter : RegexExtensions.ICustomConverter<Person>
{
public bool Convert(string groupName, Group group, Person item)
{
if (string.Equals("gender", groupName))
{
item.Gender = string.Equals(group.Value, "m") ? Gender.Male : Gender.Female;
return true;
}
return false;
}
}
public class DynamicConverter : RegexExtensions.ICustomConverter<object>
{
public bool Convert(string groupName, Group group, object item)
{
dynamic d = item;
if (string.Equals("gender", groupName))
{
d.Gender = string.Equals(group.Value, "m") ? Gender.Male : Gender.Female;
return true;
}
return false;
}
}
}
public class Person
{
public string Vorname { get; set; }
public string Nachname { get; set; }
public int Age { get; set; }
public Gender Gender { get; set; }
}
public enum Gender
{
Male,
Female
}
Viel Spass damit!
Schlagwörter: Regex, Mapper, Mapping
Schick.
Du könntest T
noch auf Referenztypen einschränken, da der Code nicht für Werttypen funktioniert (oder du passt den Code noch für Werttypen an 😃 ).
Du hast natürlich Recht. Ich habe es jetzt mal auf Referenztypen beschränkt.
Ausserdem habe ich noch eine dynamische Variante hinzugefügt.