I hadn't seen the two answers, so I went ahead and did my own implementation:
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace StringInterpolation {
/// <summary>
/// An object with an explicit, available-at-runtime name.
/// </summary>
public struct NamedObject {
public string Name;
public object Object;
public NamedObject(string name, object obj) {
Name = name;
Object = obj;
}
}
public static class StringInterpolation {
/// <summary>
/// Parses a string for basic Razor-like interpolation with explicitly passed objects.
/// For example, pass a NamedObject user, and you can use @user and @user.SomeProperty in your string.
/// </summary>
/// <param name="s">The string to be parsed.</param>
/// <param name="objects">A NamedObject array for objects too allow for parsing.</param>
public static string Interpolate(this string s, params NamedObject[] objects) {
System.Diagnostics.Debug.WriteLine(s);
List<NamedObject> namedObjects = new List<NamedObject>(objects);
Dictionary<NamedObject, Dictionary<string, string>> objectsWithProperties = new Dictionary<NamedObject, Dictionary<string, string>>();
foreach (NamedObject no in objects) {
Dictionary<string, string> properties = new Dictionary<string, string>();
foreach (System.Reflection.PropertyInfo pInfo in no.Object.GetType().GetProperties())
properties.Add(pInfo.Name, pInfo.GetValue(no.Object, new object[] { }).ToString());
objectsWithProperties.Add(no, properties);
}
foreach (Match match in Regex.Matches(s, @"@(\w+)(\.(\w+))?")) {
NamedObject no;
no = namedObjects.Find(delegate(NamedObject n) { return n.Name == match.Groups[1].Value; });
if (no.Name != null && match.Groups.Count == 4)
if (string.IsNullOrEmpty(match.Groups[3].Value))
s = s.Replace(match.Value, no.Object.ToString());
else {
Dictionary<string, string> properties = null;
string value;
objectsWithProperties.TryGetValue(no, out properties);
if (properties != null && properties.TryGetValue(match.Groups[3].Value, out value))
s = s.Replace(match.Value, value);
}
}
return s;
}
}
}
And here's a test:
using StringInterpolation;
namespace StringInterpolationTest {
class User {
public string Name { get; set; }
}
class Ticket {
public string ID { get; set; }
public string Priority { get; set; }
}
class Program {
static void Main(string[] args) {
User user = new User();
user.Name = "Joe";
Ticket previousTicket = new Ticket();
previousTicket.ID = "1";
previousTicket.Priority = "Low";
Ticket currentTicket = new Ticket();
currentTicket.ID = "1";
currentTicket.Priority = "High";
System.Diagnostics.Debug.WriteLine("User: @user, Username: @user.Name, Previous ticket priority: @previousTicket.Priority, New priority: @currentTicket.Priority".Interpolate(
new NamedObject("user", user),
new NamedObject("previousTicket", previousTicket),
new NamedObject("currentTicket", currentTicket)
));
}
}
}
It's quite a bit more code than Albin's variant, but doesn't require manually setting up IDs (though it still needs you to know ahead of time which objects to 'export' for potential interpolation).
Thanks guys!