You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为ICollection/IEnumerable元素创建Getter委托?动态环境类实现

Absolutely, you can use DynamicMethod to build efficient getter delegates that access collection elements directly—no more slow ElementAt() calls that traverse the entire collection every time. This is a perfect fit for your real-time simulation environment, and I’ll walk you through how to implement it, plus some alternative optimizations that might simplify your code.

Implementing DynamicMethod for Direct Element Access

The core idea is to generate IL at runtime that directly accesses the collection’s underlying indexer or lookup mechanism, rather than relying on generic enumeration. Let’s break this down for different collection types:

1. Indexable Collections (IList, IReadOnlyList)

For collections with an indexer (like List<T> or arrays), we can create a delegate that calls the indexer directly:

public class Environment 
{
    private delegate object GetterDelegate();
    private Dictionary<string, GetterDelegate> environmentVariables_;

    public IEnumerable<string> EnvironmentVariables => environmentVariables_.Keys;
    public object this[string name] => environmentVariables_[name]();

    public Environment() 
    {
        environmentVariables_ = new Dictionary<string, GetterDelegate>();
    }

    public void Register(string name, ICollection collection)
    {
        if (collection is IList list)
        {
            // Use DynamicMethod for index-based access
            for (int i = 0; i < list.Count; i++)
            {
                int index = i; // Capture the current index to avoid closure issues
                environmentVariables_.Add($"{name}_{index}", CreateListElementGetter(list, index));
            }
        }
        else
        {
            // Fallback for non-indexable collections (e.g., HashSet)
            // Note: This still requires enumeration, but it's unavoidable for unindexed collections
            int i = 0;
            foreach (var element in collection)
            {
                int elementIndex = i++;
                environmentVariables_.Add($"{name}_{elementIndex}", 
                    () => collection.Cast<object>().ElementAt(elementIndex));
            }
        }
    }

    private GetterDelegate CreateListElementGetter(IList list, int index)
    {
        // Create a DynamicMethod that returns the element at the given index
        var method = new DynamicMethod(
            $"GetListElement_{index}",
            typeof(object),
            new[] { typeof(IList) },
            typeof(Environment).Module);
        
        var il = method.GetILGenerator();
        // Load the list instance onto the stack
        il.Emit(OpCodes.Ldarg_0);
        // Load the index value
        il.Emit(OpCodes.Ldc_I4, index);
        // Call the IList indexer property getter
        il.Emit(OpCodes.Callvirt, typeof(IList).GetProperty("Item").GetGetMethod());
        // Return the result (non-generic IList returns object, no boxing needed)
        il.Emit(OpCodes.Ret);
        
        // Return a delegate that captures the list instance
        return () => method.Invoke(null, new object[] { list });
    }
}

2. Dictionary Collections

For Dictionary<TKey, TValue>, we can generate delegates that use the dictionary’s fast key lookup instead of converting keys to strings upfront:

public void Register<TKey, TValue>(string name, Dictionary<TKey, TValue> dictionary)
{
    foreach (var pair in dictionary)
    {
        TKey key = pair.Key;
        environmentVariables_.Add($"{name}_{key}", CreateDictionaryValueGetter(dictionary, key));
    }
}

private GetterDelegate CreateDictionaryValueGetter<TKey, TValue>(Dictionary<TKey, TValue> dictionary, TKey key)
{
    var method = new DynamicMethod(
        $"GetDictionaryValue_{key}",
        typeof(object),
        new[] { typeof(Dictionary<TKey, TValue>), typeof(TKey) },
        typeof(Environment).Module);
    
    var il = method.GetILGenerator();
    var valueLocal = il.DeclareLocal(typeof(TValue));
    
    // Load dictionary and key onto the stack
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldarg_1);
    // Load address of the local value variable
    il.Emit(OpCodes.Ldloca_S, valueLocal);
    // Call Dictionary.TryGetValue to safely look up the key
    il.Emit(OpCodes.Callvirt, typeof(Dictionary<TKey, TValue>).GetMethod("TryGetValue"));
    
    // If TryGetValue returns true, return the value; else return null
    var notFoundLabel = il.DefineLabel();
    il.Emit(OpCodes.Brfalse_S, notFoundLabel);
    il.Emit(OpCodes.Ldloc_S, valueLocal);
    if (typeof(TValue).IsValueType)
    {
        il.Emit(OpCodes.Box, typeof(TValue)); // Box value types to object
    }
    il.Emit(OpCodes.Ret);
    
    il.MarkLabel(notFoundLabel);
    il.Emit(OpCodes.Ldnull);
    il.Emit(OpCodes.Ret);
    
    return () => method.Invoke(null, new object[] { dictionary, key });
}

3. Field Access

For FieldInfo, we can generate delegates that directly access the field (instance or static) without reflection overhead:

public void Register(string name, FieldInfo field, object target = null)
{
    environmentVariables_.Add(name, CreateFieldGetter(field, target));
}

private GetterDelegate CreateFieldGetter(FieldInfo field, object target)
{
    var method = new DynamicMethod(
        $"GetField_{field.Name}",
        typeof(object),
        target == null ? Type.EmptyTypes : new[] { typeof(object) },
        typeof(Environment).Module);
    
    var il = method.GetILGenerator();
    if (target != null)
    {
        // Load the target instance and cast it to the field's declaring type
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Castclass, field.DeclaringType);
    }
    // Load the field value
    il.Emit(field.IsStatic ? OpCodes.Ldsfld : OpCodes.Ldfld, field);
    // Box value types if needed
    if (field.FieldType.IsValueType)
    {
        il.Emit(OpCodes.Box, field.FieldType);
    }
    il.Emit(OpCodes.Ret);
    
    // Return a delegate that captures the target (if needed)
    return target == null 
        ? () => method.Invoke(null, null) 
        : () => method.Invoke(null, new[] { target });
}
Alternative: Use Expression Trees for Readability

If you find writing IL with DynamicMethod too low-level, Expression Trees offer a more readable alternative that compiles to the same efficient IL. For example, here’s how to rewrite the list element getter:

private GetterDelegate CreateListElementGetter(IList list, int index)
{
    var listParam = Expression.Parameter(typeof(IList));
    var indexExpr = Expression.Constant(index);
    var propertyAccess = Expression.Property(listParam, "Item", indexExpr);
    var lambda = Expression.Lambda<Func<IList, object>>(propertyAccess, listParam);
    var func = lambda.Compile();
    
    return () => func(list);
}

This achieves the same performance as DynamicMethod but is much easier to write and debug.

Key Fixes for Your Original Code

One critical issue in your initial code is that it captures elements at registration time—if the collection changes later, the getter will return the old value. The approaches above fix this by accessing the collection directly when the getter is called, ensuring you always get real-time data.

Additional Best Practices
  • Check for Indexable Interfaces First: Always prioritize IList, IReadOnlyList, or IDictionary when registering collections—these offer the fastest access patterns.
  • Handle Collection Changes: For dynamic collections that update frequently, consider using INotifyCollectionChanged to automatically refresh your environment variables when the collection changes.
  • Cache Compiled Delegates: If you register many instances of the same collection type, cache the compiled delegates to avoid re-generating IL/Expressions every time.

内容的提问来源于stack exchange,提问作者Haus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:01:23