Evaluates a selector.
Implement this interface, or derive from Source, to resolve placeholder selectors from your own value types, then register the source on the SmartFormatter. The module does not ship a reflection-based source; the example shows how to write one that looks up members by name.
using System; using System.Collections.Generic; using System.Reflection; using Unity.SmartStrings.Core.Extensions;
// Sample: a custom ISource that resolves a placeholder selector against an // arbitrary object using System.Reflection. The runtime module does not ship a // reflection source, so projects that want member-name lookup can add one like // this to their own SmartFormatter. [Serializable] so it round-trips when the // SmartFormatter it belongs to is serialized. [Serializable] public class ReflectionSource : Source { readonly Dictionary<(Type, string), (FieldInfo field, MethodInfo method)> m_Cache = new();
public override bool TryEvaluateSelector(ISelectorInfo selectorInfo) { var current = selectorInfo.CurrentValue;
if (TrySetResultForNullableOperator(selectorInfo)) return true;
// strings are handled by StringSource if (current is null or string) return false;
var selector = selectorInfo.SelectorText; var sourceType = current.GetType();
if (m_Cache.TryGetValue((sourceType, selector), out var found)) { if (found.field != null) { selectorInfo.Result = found.field.GetValue(current); return true; }
if (found.method != null) { selectorInfo.Result = found.method.Invoke(current, Array.Empty<object>()); return true; }
return false; }
if (EvaluateMembers(selectorInfo, selector, current, sourceType)) return true;
// Cache failures too, so we do not call GetMembers again for this pair. m_Cache[(sourceType, selector)] = (null, null); return false; }
bool EvaluateMembers(ISelectorInfo selectorInfo, string selector, object current, Type sourceType) { const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public; var comparison = selectorInfo.FormatDetails.Settings.GetCaseSensitivityComparison();
foreach (var member in sourceType.GetMembers(bindingFlags)) { if (!string.Equals(member.Name, selector, comparison)) continue;
switch (member.MemberType) { case MemberTypes.Field: var field = member as FieldInfo; selectorInfo.Result = field?.GetValue(current); m_Cache[(sourceType, selector)] = (field, null); return true; case MemberTypes.Property: case MemberTypes.Method: if (!TryGetMethodInfo(member, out var method)) continue;
// Only parameterless, non-void members can be used as selectors. if (method?.GetParameters().Length > 0) continue; if (method?.ReturnType == typeof(void)) continue;
m_Cache[(sourceType, selector)] = (null, method); selectorInfo.Result = method?.Invoke(current, Array.Empty<object>()); return true; } }
return false; }
static bool TryGetMethodInfo(MemberInfo member, out MethodInfo method) { if (member.MemberType == MemberTypes.Property) { if (member is PropertyInfo { CanRead: true } prop) { method = prop.GetGetMethod(); return true; }
method = null; return false; }
method = member as MethodInfo; return true; } }
| Method | Description |
|---|---|
| TryEvaluateSelector | Evaluates the Selector based on the ISelectorInfo.CurrentValue. |