private void AppendToConstructorFromEvent(EventInfo info, MemberMetadata metadata, CSharpSourceWriter deferWriter) { var addMethod = info.AddMethod; if (addMethod.IsAbstract && !addMethod.IsAssembly && !addMethod.IsFamilyAndAssembly) { stubWriter.Write($"if ({metadata.Name} == null) {metadata.Name} = new EventImplementation<{metadata.HandlerArgsType}>();"); deferWriter.Write($"stub.{metadata.Name} = new EventImplementation<{metadata.HandlerArgsType}>();"); } if (addMethod.IsStatic || addMethod.IsPrivate || !addMethod.IsVirtual || addMethod.IsAbstract || addMethod.IsAssembly || addMethod.IsFamilyAndAssembly) { return; } stubWriter.Write($"if ({metadata.Name} == null)"); using (stubWriter.Scope) { stubWriter.Write($"{metadata.Name} = new EventImplementation<{metadata.HandlerArgsType}>();"); stubWriter.Write($"{metadata.Name}.add = value => Base{metadata.Name}Add(new {metadata.HandlerType}(value));"); stubWriter.Write($"{metadata.Name}.remove = value => Base{metadata.Name}Remove(new {metadata.HandlerType}(value));"); } deferWriter.Write($"stub.{metadata.Name} = new EventImplementation<{metadata.HandlerArgsType}>();"); deferWriter.Write($"stub.{metadata.Name}.add = value => stub.Base{metadata.Name}Add(new {metadata.HandlerType}(value));"); deferWriter.Write($"stub.{metadata.Name}.remove = value => stub.Base{metadata.Name}Remove(new {metadata.HandlerType}(value));"); }
private void AppendConstructor(Type type, ConstructorInfo info, MemberMetadata constructorMetadata) { if (info.IsPrivate || info.IsStatic || info.IsFamilyAndAssembly || info.IsAssembly) { return; } var typeName = type.CreateCsName(type.Namespace); var(basename, genericInfo) = typeName.ExtractImplementationNameParts("<"); var intermediateName = $"IntermediateStub{basename}_DoNotUse"; var stubName = $"Stub{basename}"; // for every constructor, make both a constructor and a static "DeferConstruction" method var deferWriter = new CSharpSourceWriter(stubWriter.Indentation); stubWriter.Write($"public {stubName}({constructorMetadata.ParameterTypesAndNames}) : base({constructorMetadata.ParameterNames})"); var outParam = $"out {stubTypeName} uninitializedStub"; var separator = constructorMetadata.ParameterTypesAndNames.Length > 0 ? ", " : string.Empty; deferWriter.Write($"public static IDisposable DeferConstruction({constructorMetadata.ParameterTypesAndNames}{separator}{outParam})"); using (stubWriter.Scope) { using (deferWriter.Scope) { deferWriter.Write($"{stubTypeName} stub;"); deferWriter.Write($"var disposable = ConstructionCompletion.CreateObjectWithDeferredConstruction<{stubTypeName}>(out stub{separator}{constructorMetadata.ParameterNames});"); foreach (var member in Program.FindAllMembers(type)) { var metadata = new MemberMetadata(member, type.Namespace); switch (member.MemberType) { case MemberTypes.Method: AppendToConstructorFromMethod((MethodInfo)member, metadata, deferWriter); break; case MemberTypes.Event: AppendToConstructorFromEvent((EventInfo)member, metadata, deferWriter); break; case MemberTypes.Property: AppendToConstructorFromProperty((PropertyInfo)member, metadata, deferWriter); break; default: // the only other options are Field, Type, NestedType, and Constructor // none of those can be virtual/abstract, so we don't need to put anything in the constructor for them. break; } } deferWriter.Write($"uninitializedStub = stub;"); deferWriter.Write($"return disposable;"); } } stubWriter.Write(deferWriter.ToString()); intermediateWriter.Write($"protected {intermediateName}({constructorMetadata.ParameterTypesAndNames}) : base({constructorMetadata.ParameterNames}) {{ }}"); }
/// <summary> /// Creates a Builder of the given generic type to implement the given interface. /// Output is placed in the given fileName. /// </summary> private static void GenerateImplementation <TPatternBuilder>(Type type) where TPatternBuilder : IPatternBuilder { var writer = new CSharpSourceWriter(numberOfSpacesToIndent: 4); var builder = (TPatternBuilder)Activator.CreateInstance(typeof(TPatternBuilder), writer); var fileName = builder.GetDesiredOutputFileName(type); Console.WriteLine($"Generating {fileName} ..."); writer.Write($"// this file was created by AutoImplement"); writer.Write($"namespace {type.Namespace}"); using (writer.Scope) { writer.Write($"public class {builder.ClassDeclaration(type)}"); using (writer.Scope) { builder.AppendExtraMembers(type); var allMembers = FindAllMembers(type); foreach (var member in allMembers) { var metadata = new MemberMetadata(member, type.Namespace); switch (member.MemberType) { case MemberTypes.Method: ImplementMethod(member, metadata, builder); break; case MemberTypes.Event: ImplementEvent(member, metadata, builder); break; case MemberTypes.Property: ImplementProperty(member, metadata, builder); break; default: // the only other options are Field, Type, NestedType, and Constructor // for classes, any of these are possible, and all can be ignored. break; } } } builder.BuildCompleted(); } File.WriteAllText(fileName, writer.ToString()); }
private void AppendToConstructorFromMethod(MethodInfo info, MemberMetadata metadata, CSharpSourceWriter deferWriter) { if (info.IsSpecialName || info.IsStatic || info.IsPrivate || !info.IsVirtual || info.IsAbstract || info.IsAssembly || info.IsFamilyAndAssembly || info.IsGenericMethod) { return; } if (info.IsVirtual && info.Name == "Finalize") { return; // Finalize is special in C#. Use a destructor instead. } var typesExtension = StubBuilder.SanitizeMethodName(metadata.ParameterTypes); var methodsWithMatchingNameButNotSignature = implementedMethods.Where(name => name.Split('(')[0] == metadata.Name && name != $"{metadata.Name}({metadata.ParameterTypes})"); string localImplementationName = methodsWithMatchingNameButNotSignature.Any() ? $"{metadata.Name}_{typesExtension}" : metadata.Name; stubWriter.Write($"if ({localImplementationName} == null) {localImplementationName} = Base{metadata.Name};"); deferWriter.Write($"stub.{localImplementationName} = stub.Base{metadata.Name};"); implementedMethods.Add($"{metadata.Name}({metadata.ParameterTypes})"); }
// <example> // public void DoThing(int arg) // { // for (int i = 0; i < base.Count; i++) // { // base[i].DoThing(arg); // } // } // </example> /// <remarks> /// Composite methods with return types are a bit strange. /// If all the methods agree on what to return, then return that. /// If any are different, then just return default. /// In the case of nullables and bools, this default seems appropriate. /// But it can be strange for numeric types. /// /// For methods that return void, a composite simply forwards the method call down to each thing that it contains. /// </remarks> public void AppendMethod(MethodInfo info, MemberMetadata method) { // Use an explicit implementation only if the signature has already been used // example: IEnumerable<T>, which extends IEnumerable if (!implementedMethods.Any(name => name == $"{method.Name}({method.ParameterTypes})")) { writer.Write($"public virtual {method.ReturnType} {method.Name}{method.GenericParameters}({method.ParameterTypesAndNames}){method.GenericParameterConstraints}"); } else { writer.Write($"{method.ReturnType} {method.DeclaringType}.{method.Name}{method.GenericParameters}({method.ParameterTypesAndNames}){method.GenericParameterConstraints}"); } using (writer.Scope) { writer.AssignDefaultValuesToOutParameters(info.DeclaringType.Namespace, info.GetParameters()); if (method.ReturnType == "void") { writer.Write("for (int i = 0; i < base.Count; i++)"); using (writer.Scope) { writer.Write($"base[i].{method.Name}{method.GenericParameters}({method.ParameterNames});"); } } else { writer.Write($"var results = new System.Collections.Generic.List<{method.ReturnType}>();"); writer.Write("for (int i = 0; i < base.Count; i++)"); using (writer.Scope) { writer.Write($"results.Add(base[i].{method.Name}{method.GenericParameters}({method.ParameterNames}));"); } writer.Write("if (results.Count > 0 && results.All(result => result.Equals(results[0])))"); using (writer.Scope) { writer.Write("return results[0];"); } writer.Write($"return default({method.ReturnType});"); } } writer.Write(string.Empty); implementedMethods.Add($"{method.Name}({method.ParameterTypes})"); }
/// <example> // public Func<int, int, int> Max { get; set; } // int ICalculator.Max(int a, int b) // { // if (Max != null) // { // return this.Max(a, b); // } // else // { // return default(int); // } // } // // public Func<double, double, double> Max_double_double { get; set; } // double ICalculator.Max(double a, double b) // { // if (Max_double_double != null) // { // return this.Max_double_double(a, b); // } // else // { // return default(double); // } // } /// </example> /// <remarks> /// Methods in interfaces are replaced with delegate properties. /// Assigning one of those delegates a value will change the behavior of that method. /// You can call the delegate just like the method. /// When the interface method is called, it will call the delegate method if possible. /// If there is no delegate, it returns default. /// </remarks> public void AppendMethod(MethodInfo info, MemberMetadata method) { if (info.IsGenericMethodDefinition) { AppendGenericMethod(info, method); return; } var delegateName = GetStubName(method.ReturnType, method.ParameterTypes); var typesExtension = SanitizeMethodName(method.ParameterTypes); var methodsWithMatchingNameButNotSignature = implementedMethods.Where(name => name.Split('(')[0] == method.Name && name != $"{method.Name}({method.ParameterTypes})"); string localImplementationName = methodsWithMatchingNameButNotSignature.Any() ? $"{method.Name}_{typesExtension}" : method.Name; if (info.GetParameters().Any(p => p.ParameterType.IsByRef)) { localImplementationName = $"{method.Name}_{typesExtension}"; delegateName = $"{method.Name}Delegate_{typesExtension}"; writer.Write($"public delegate {method.ReturnType} {delegateName}({method.ParameterTypesAndNames});" + Environment.NewLine); } // only add a delegation property for the first method with a given signature // this is important for IEnumerable<T>.GetEnumerator() and IEnumerable.GetEnumerator() -> same name, same signature if (!implementedMethods.Any(name => name == $"{method.Name}({method.ParameterTypes})")) { writer.Write($"public {delegateName} {localImplementationName} {{ get; set; }}" + Environment.NewLine); } ImplementInterfaceMethod(info, localImplementationName, method); writer.Write(string.Empty); implementedMethods.Add($"{method.Name}({method.ParameterTypes})"); }
public void AppendMethod(MethodInfo info, MemberMetadata metadata) { if (info.IsStatic || info.IsPrivate || info.IsAssembly || info.IsFamilyAndAssembly) { return; } if (!info.IsVirtual && !info.IsAbstract) { if (metadata.Access == "protected") { // the member is protected. Make a public version. stubWriter.Write($"public new {metadata.ReturnType} {metadata.Name}{metadata.GenericParameters}({metadata.ParameterTypesAndNames}){metadata.GenericParameterConstraints}"); using (stubWriter.Scope) { stubWriter.Write($"{metadata.ReturnClause}base.{metadata.Name}({metadata.ParameterNames});"); } } return; } if (info.IsGenericMethodDefinition) { AppendGenericMethod(info, metadata); return; } var delegateName = GetDelegateName(metadata.ReturnType, metadata.ParameterTypes); var typesExtension = StubBuilder.SanitizeMethodName(metadata.ParameterTypes); var methodsWithMatchingNameButNotSignature = implementedMethods.Where(name => name.Split('(')[0] == metadata.Name && name != $"{metadata.Name}({metadata.ParameterTypes})"); string localImplementationName = methodsWithMatchingNameButNotSignature.Any() ? $"{metadata.Name}_{typesExtension}" : metadata.Name; if (info.GetParameters().Any(p => p.ParameterType.IsByRef)) { delegateName = $"{metadata.Name}Delegate_{typesExtension}"; stubWriter.Write($"public delegate {metadata.ReturnType} {delegateName}({metadata.ParameterTypesAndNames});" + Environment.NewLine); } stubWriter.Write($"public new {delegateName} {localImplementationName};"); WriteHelperBaseMethod(info, metadata); WriteHelperMethod(info, metadata, stubTypeName, localImplementationName); implementedMethods.Add($"{metadata.Name}({metadata.ParameterTypes})"); }
private void AppendToConstructorFromProperty(PropertyInfo info, MemberMetadata metadata, CSharpSourceWriter deferWriter) { var method = info.GetMethod ?? info.SetMethod; if (method.IsAbstract && !method.IsAssembly && !method.IsFamilyAndAssembly) { if (info.Name == "Item" && info.GetIndexParameters().Length > 0) { // item property maps to two methods, get_Item and set_Item // but since the property is abstract, we have no base implementation // so just like methods, give no default values in the constructor (or defer construction call) } else { stubWriter.Write($"if ({metadata.Name} == null) {metadata.Name} = new PropertyImplementation<{metadata.ReturnType}>();"); deferWriter.Write($"stub.{metadata.Name} = new PropertyImplementation<{metadata.ReturnType}>();"); } } if (method.IsStatic || method.IsPrivate || !method.IsVirtual || method.IsAbstract || method.IsAssembly || method.IsFamilyAndAssembly) { return; } if (info.Name == "Item" && info.GetIndexParameters().Length > 0) { if (info.CanRead) { stubWriter.Write($"if (get_Item == null) get_Item = Base_get_Item;"); } if (CanWrite(info)) { stubWriter.Write($"if (set_Item == null) set_Item = Base_set_Item;"); } if (info.CanRead) { deferWriter.Write($"stub.get_Item = stub.Base_get_Item;"); } if (CanWrite(info)) { deferWriter.Write($"stub.set_Item = stub.Base_set_Item;"); } } else { stubWriter.Write($"if ({metadata.Name} == null)"); using (stubWriter.Scope) { stubWriter.Write($"{metadata.Name} = new PropertyImplementation<{metadata.ReturnType}>();"); if (info.CanRead) { stubWriter.Write($"{metadata.Name}.get = () => Base{metadata.Name};"); } if (CanWrite(info)) { stubWriter.Write($"{metadata.Name}.set = value => Base{metadata.Name} = value;"); } } deferWriter.Write($"stub.{metadata.Name} = new PropertyImplementation<{metadata.ReturnType}>();"); if (info.CanRead) { deferWriter.Write($"stub.{metadata.Name}.get = () => stub.Base{metadata.Name};"); } if (CanWrite(info)) { deferWriter.Write($"stub.{metadata.Name}.set = value => stub.Base{metadata.Name} = value;"); } } }