public static EnumerableInfo FindMethods(Type t) { var ie = new EnumerableInfo(); var methods = t.GetMethods(BindingFlags.Instance | BindingFlags.Public); var getEnumeratorCandidates = methods .Where(mi => mi.Name == "GetEnumerator" && mi.GetParameters().Length == 0 && mi.ReturnType != typeof(object) && mi.ReturnType != typeof(void)) .ToList(); var genericEnumerable = GetGenericEnumerable(t); if (genericEnumerable != null) { getEnumeratorCandidates.Add(genericEnumerable.GetMethod("GetEnumerator")); } getEnumeratorCandidates.Sort(SortEnumeratorCandidates); ie.GetEnumerator = getEnumeratorCandidates.FirstOrDefault(); var genericCollection = GetGenericCollection(t); // only use get_Count if ICollection<> or IReadOnlyCollection<> are implemented if (genericCollection != null) { ie.get_Count = methods .FirstOrDefault(mi => mi.IsSpecialName && mi.ReturnType == typeof(int) && mi.Name == "get_Count" && mi.GetParameters().Length == 0) ?? genericCollection.GetMethod("get_Count"); } if (ie.GetEnumerator == null) { return(ie); } var enumeratorType = ie.GetEnumerator.ReturnType; methods = enumeratorType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); ie.MoveNext = methods.FirstOrDefault(mi => mi.Name == "MoveNext"); ie.Dispose = methods.FirstOrDefault(mi => mi.Name == "Dispose"); ie.get_Current = methods.FirstOrDefault(mi => mi.IsSpecialName && mi.Name == "get_Current"); if (ie.MoveNext == null && typeof(System.Collections.IEnumerator).IsAssignableFrom(enumeratorType)) { ie.MoveNext = _MoveNext; } if (ie.Dispose == null && typeof(System.IDisposable).IsAssignableFrom(enumeratorType)) { ie.Dispose = _Dispose; } return(ie); }
void EmitEnumerable(Schema schema, bool pushResult, bool isArray) { var enumerable = EnumerableInfo.FindMethods(schema.NetType); MethodInfo getKey = null; MethodInfo getValue = null; if (!isArray) { getKey = enumerable.get_Current.ReturnType.GetProperty("Key").GetMethod; getValue = enumerable.get_Current.ReturnType.GetProperty("Value").GetMethod; } var ifNotNull = DefineLabel("ifNotNull"); var ifNull = DefineLabel("ifNull"); Emit.Duplicate(); // preserve the value Emit.BranchIfTrue(ifNotNull); Emit.Pop(); // discard value if (pushResult) { Emit.Pop(); // discard avail } WriteConstant("null", push: pushResult); Emit.Branch(ifNull); Emit.MarkLabel(ifNotNull); int localDepth = pushResult ? 2 : 1; Depth += localDepth; // begin array WriteConstant(isArray ? "[" : "{"); var loopBottom = DefineLabel("loopBottom"); var loopTop = DefineLabel("loopTop"); var closeArray = DefineLabel("closeArray"); // call GetEnumerator CallCorrectly(enumerable.GetEnumerator, schema.NetType); var enumeratorType = enumerable.GetEnumerator.ReturnType; // use the pointer for enumerator structs if (enumeratorType.IsValueType) { PushAddress(enumeratorType); } // FIXME need to have some support for Dispose //var exceptionBlock = enumerable.Dispose == null ? null : Emit.BeginExceptionBlock(); // unroll the first iteration so the loop can always write the comma /* if (!enumerator.MoveNext()) goto closeArray; */ Emit.Duplicate(); // preserve enumerator CallCorrectly(enumerable.MoveNext, enumeratorType); Emit.BranchIfFalse(closeArray); // push enumerator.Current Emit.Duplicate(); // preserve enumerator CallCorrectly(enumerable.get_Current, enumeratorType); if (!isArray) { // we've got a KeyValuePair<,> PushAddress(getValue.DeclaringType); Emit.Duplicate(); // preserve KeyValuePair<,>* Emit.Call(getKey); // write the first key Depth++; EmitInline(schema.Keys); WriteConstant(":"); Depth--; Emit.Call(getValue); } // write the first element EmitInline(schema.Items); Emit.Branch(loopBottom); Emit.MarkLabel(loopTop); // write ',' WriteConstant(","); // push enumerator.Current Emit.Duplicate(); // preserve enumerator CallCorrectly(enumerable.get_Current, enumeratorType); if (!isArray) { // we've got a KeyValuePair<,> PushAddress(getValue.DeclaringType); Emit.Duplicate(); // preserve KeyValuePair<,>* Emit.Call(getKey); // write the key Depth++; EmitInline(schema.Keys); WriteConstant(":"); Depth--; Emit.Call(getValue); } // write the element EmitInline(schema.Items); /* endLoop: */ Emit.MarkLabel(loopBottom); /* if (enumerator.MoveNext()) goto beginLoop; */ Emit.Duplicate(); // preserve enumerator CallCorrectly(enumerable.MoveNext, enumeratorType); Emit.BranchIfTrue(loopTop); Emit.MarkLabel(closeArray); // this is where the finally block would go //if (exceptionBlock != null) //{ // var f = Emit.BeginFinallyBlock(exceptionBlock); // CallCorrectly(enumerable.Dispose, enumeratorType); // Emit.EndFinallyBlock(f); // Emit.EndExceptionBlock(exceptionBlock); //} //else Emit.Pop(); // discard enumerator // end array Depth--; WriteConstant(isArray ? "]" : "}"); Depth++; if (pushResult) { // push bytes written Emit.LoadLocal(LocalAvailable); Emit.Subtract(); } if (schema.Nullable) { Emit.MarkLabel(ifNull); } Depth -= localDepth; }