# ISource

> Evaluates a selector.

## Definition

* **Type:** Interface
* **Namespace:** [Unity.SmartStrings.Core.Extensions](/engine/6000.7/script-reference/unity/smartstrings/core/extensions.md)
* **Assembly:** UnityEngine.SmartStringsModule

```csharp
public interface ISource
```

## Remarks

Implement this interface, or derive from [Source](/engine/6000.7/script-reference/unity/smartstrings/core/extensions/source.md), to resolve placeholder selectors from your own value types, then register the source on the [SmartFormatter](/engine/6000.7/script-reference/unity/smartstrings/smartformatter.md). The module does not ship a reflection-based source; the example shows how to write one that looks up members by name.

## Examples

```csharp
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;
    }
}
```

## Methods

| Method                                                                                                                   | Description                                                                                                                                                                                                                                        |
| ------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [TryEvaluateSelector](/engine/6000.7/script-reference/unity/smartstrings/core/extensions/isource/tryevaluateselector.md) | Evaluates the [Selector](/engine/6000.7/script-reference/unity/smartstrings/core/parsing/selector.md) based on the [ISelectorInfo.CurrentValue](/engine/6000.7/script-reference/unity/smartstrings/core/extensions/iselectorinfo/currentvalue.md). |
