// <example> // public event EventHandler MyEvent // { // add // { // this.ForEach(listItem => listItem.MyEvent += value; // } // remove // { // this.ForEach(listItem => listItem.MyEvent -= value; // } // } // </example> /// <remarks> /// Event implementations are allowed to provide bodies for what to do in the add/remove cases. /// In this case, the most appropriate action is just to forward the notification down to the contained items. /// </remarks> public void AppendEvent(EventInfo info, MemberMetadata eventData) { writer.Write($"public virtual event {eventData.HandlerType} {eventData.Name}"); using (writer.Scope) { writer.Write("add"); using (writer.Scope) { writer.Write($"this.ForEach(listItem => listItem.{eventData.Name} += value);"); } writer.Write("remove"); using (writer.Scope) { writer.Write($"this.ForEach(listItem => listItem.{eventData.Name} -= 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}) {{ }}"); }
public string ClassDeclaration(Type type) { var typeName = type.CreateCsName(type.Namespace); string basename; (basename, genericInfo) = typeName.ExtractImplementationNameParts("<"); constraints = MemberMetadata.GetGenericParameterConstraints(type.GetGenericArguments(), type.Namespace); intermediateWriter.Write($"public class IntermediateStub{basename}_DoNotUse{genericInfo} : {typeName}{constraints}"); Debug.Assert(intermediateClassScope == null); intermediateClassScope = intermediateWriter.Scope; stubTypeName = $"Stub{basename}{genericInfo}"; return($"{stubTypeName} : IntermediateStub{basename}_DoNotUse{genericInfo}{constraints}"); }
// <example> // public EventImplementation<EventArgs> ValueChanged = new EventImplementation<EventHandler>(); // // event EventHandler INotifyValueChanged.ValueChanged // { // add // { // ValueChanged.add(new EventHandler<EventArgs>(value)); // } // remove // { // ValueChanged.remove(new EventHandler<EventArgs>(value)); // } // } // </example> // <remarks> // Events are replaces with an EventImplementation field. // Explicit interface implementations then call that EventImplementation. // // EventImplementation exposes add, remove, handlers, and +/- operators along with an Invoke method. // This allows you to assign custom add/remove handlers to the Stub, or make decision based on the individual added handlers, // or use +=, -=, and .Invoke as if the EventImplementation were actually an event. // // Note that the explicit implementation always casts added/removed delegates to EventHandler<T>. // This is to avoid having to deal with .net's 2 types of EventHandlers separately. // Example: RoutedEventHandler vs EventHandler<RoutedEventArgs>. // </remarks> public void AppendEvent(EventInfo info, MemberMetadata eventData) { writer.Write($"public EventImplementation<{eventData.HandlerArgsType}> {info.Name} = new EventImplementation<{eventData.HandlerArgsType}>();"); writer.Write(string.Empty); writer.Write($"event {eventData.HandlerType} {eventData.DeclaringType}.{info.Name}"); using (writer.Scope) { writer.Write("add"); using (writer.Scope) { writer.Write($"{info.Name}.add(new EventHandler<{eventData.HandlerArgsType}>(value));"); } writer.Write("remove"); using (writer.Scope) { writer.Write($"{info.Name}.remove(new EventHandler<{eventData.HandlerArgsType}>(value));"); } } }
/// <summary> /// Normally, making a stub method on a baseclass requires two overrides with the same name. /// An override method, and a new delegate field with the same name. /// But for generic methods, a delegate field with the same name won't work. /// Instead, we have a generic delegate, a dictionary for overrides, a caller for the base method, an "ImplementMember" method, and an override for the method. /// Since all of these have different names, we can put all the overrides in a single class. /// So don't put anything in the helper intermediate class. /// </summary> private void AppendGenericMethod(MethodInfo info, MemberMetadata metadata) { var typesExtension = StubBuilder.SanitizeMethodName(metadata.ParameterTypes); var typeofList = info.GetGenericArguments().Select(type => $"typeof({type.Name})").Aggregate((a, b) => $"{a}, {b}"); var createKey = $"var key = new Type[] {{ {typeofList} }};"; var delegateName = $"{metadata.Name}Delegate_{typesExtension}{metadata.GenericParameters}"; var dictionary = $"{metadata.Name}Delegates_{typesExtension}"; var methodName = $"{metadata.Name}{metadata.GenericParameters}"; stubWriter.Write($"public delegate {metadata.ReturnType} {delegateName}({metadata.ParameterTypesAndNames}){metadata.GenericParameterConstraints};"); stubWriter.Write($"private readonly Dictionary<Type[], object> {dictionary} = new Dictionary<Type[], object>(new EnumerableEqualityComparer<Type>());"); stubWriter.Write($"public void Implement{methodName}({delegateName} implementation){metadata.GenericParameterConstraints}"); using (stubWriter.Scope) { stubWriter.Write(createKey); stubWriter.Write($"{dictionary}[key] = implementation;"); } if (!info.IsAbstract) { stubWriter.Write($"public {metadata.ReturnType} Base{methodName}({metadata.ParameterTypesAndNames}){metadata.GenericParameterConstraints}"); using (stubWriter.Scope) { stubWriter.Write($"{metadata.ReturnClause}base.{methodName}({metadata.ParameterNames});"); } } stubWriter.Write($"{metadata.Access} override {metadata.ReturnType} {methodName}({metadata.ParameterTypesAndNames})"); using (stubWriter.Scope) { stubWriter.AssignDefaultValuesToOutParameters(info.DeclaringType.Namespace, info.GetParameters()); stubWriter.Write(createKey); stubWriter.Write("object implementation;"); stubWriter.Write($"if ({dictionary}.TryGetValue(key, out implementation))"); using (stubWriter.Scope) { stubWriter.Write($"{metadata.ReturnClause}(({delegateName})implementation).Invoke({metadata.ParameterNames});"); } stubWriter.Write("else"); using (stubWriter.Scope) { if (!info.IsAbstract) { stubWriter.Write($"{metadata.ReturnClause}Base{methodName}({metadata.ParameterNames});"); } else if (metadata.ReturnType != "void") { stubWriter.Write($"return default({metadata.ReturnType});"); } } } }
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 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})"); }
private void WriteHelperMethod(MethodInfo info, MemberMetadata metadata, string stubTypeName, string localImplementationName) { intermediateWriter.Write($"{metadata.Access} override {metadata.ReturnType} {metadata.Name}({metadata.ParameterTypesAndNames}){metadata.GenericParameterConstraints}"); using (intermediateWriter.Scope) { var call = $"(({stubTypeName})this).{localImplementationName}"; intermediateWriter.AssignDefaultValuesToOutParameters(info.DeclaringType.Namespace, info.GetParameters()); intermediateWriter.Write($"if ({call} != null)"); using (intermediateWriter.Scope) { intermediateWriter.Write($"{metadata.ReturnClause}{call}({metadata.ParameterNames});"); } if (metadata.ReturnType != "void") { intermediateWriter.Write("else"); using (intermediateWriter.Scope) { intermediateWriter.Write($"return default({metadata.ReturnType});"); } } } }
private void AppendPropertyCommon(PropertyInfo info, MemberMetadata property, string listItem) { using (writer.Scope) { if (info.CanRead) { writer.Write("get"); using (writer.Scope) { writer.Write($"var results = this.Select<{property.DeclaringType}, {property.ReturnType}>(listItem => {listItem}).ToList();"); writer.Write($"return results.Count > 0 && results.All(result => result.Equals(results[0])) ? results[0] : default({property.ReturnType});"); } } if (info.CanWrite) { writer.Write("set"); using (writer.Scope) { writer.Write($"this.ForEach(listItem => {listItem} = value);"); } } } }
// <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})"); }
/// <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()); }
/// <remarks> /// Since Item properties in .net have parameters, the Item property has to be handled specially. /// Instead of using a PropertyImplementation object, two separate delegates are exposed, named get_Item and set_Item. /// The get and set of the Item property forward to these two public fields. /// If no implementation is provided, get_Item will just return default. /// </remarks> public void AppendItemProperty(PropertyInfo info, MemberMetadata property) { // define the backing get/set_Item methods if this property hasn't been implemented yet if (!implementedProperties.Contains(property.Name)) { if (info.CanRead) { writer.Write($"public Func<{property.ParameterTypes}, {property.ReturnType}> get_Item = ({property.ParameterNames}) => default({property.ReturnType});" + Environment.NewLine); } if (info.CanWrite) { writer.Write($"public Action<{property.ParameterTypes}, {property.ReturnType}> set_Item = ({property.ParameterNames}, value) => {{}};" + Environment.NewLine); } implementedProperties.Add(property.Name); } // define the explicit interface implementation // this may run multiple times if the same property is defined on multiple interfaces (example, IReadOnlyList and IList) writer.Write($"{property.ReturnType} {property.DeclaringType}.this[{property.ParameterTypesAndNames}]"); using (writer.Scope) { if (info.CanRead) { writer.Write("get"); using (writer.Scope) { writer.Write($"return get_Item({property.ParameterNames});"); } } if (info.CanWrite) { writer.Write("set"); using (writer.Scope) { writer.Write($"set_Item({property.ParameterNames}, value);"); } } } }
public void AppendExtraMembers(Type type) { // add in constructors var constructors = type.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic) .Concat(type.GetConstructors(BindingFlags.Instance | BindingFlags.Public)); foreach (var constructor in constructors) { var metadata = new MemberMetadata(constructor, type.Namespace); AppendConstructor(type, constructor, metadata); } implementedMethods.Clear(); // add in fields var protectedFields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic).Where(field => field.IsFamily || field.IsFamilyOrAssembly); foreach (var field in protectedFields) { var metadata = new MemberMetadata(field, type.Namespace); AppendField(type, field, metadata); } }
public void AppendItemProperty(PropertyInfo info, MemberMetadata metadata) { var methodInfo = info.GetMethod ?? info.SetMethod; if (methodInfo.IsStatic || methodInfo.IsPrivate || methodInfo.IsAssembly || methodInfo.IsFamilyAndAssembly) { return; } if (!methodInfo.IsVirtual && !methodInfo.IsAbstract) { // the member is protected. Make a public version. if (metadata.Access == "protected") { intermediateWriter.Write($"public new {metadata.ReturnType} this[{metadata.ParameterTypesAndNames}]"); using (intermediateWriter.Scope) { if (info.CanRead) { intermediateWriter.Write($"get {{ return base[{metadata.ParameterNames}]; }}"); } if (info.CanWrite) { intermediateWriter.Write($"set {{ base[{metadata.ParameterNames}] = value; }}"); } } } return; } if (info.CanRead) { stubWriter.Write($"public new Func<{metadata.ParameterTypes}, {metadata.ReturnType}> get_Item;"); } if (info.CanWrite) { stubWriter.Write($"public new Action<{metadata.ParameterTypes}, {metadata.ReturnType}> set_Item;"); } if (methodInfo.IsVirtual && !methodInfo.IsAbstract) { if (info.CanRead) { intermediateWriter.Write($"public {metadata.ReturnType} Base_get_Item({metadata.ParameterTypesAndNames})"); using (intermediateWriter.Scope) { intermediateWriter.Write($"return base[{metadata.ParameterNames}];"); } } if (info.CanWrite) { intermediateWriter.Write($"public void Base_set_Item({metadata.ParameterTypesAndNames}, {metadata.ReturnType} value)"); using (intermediateWriter.Scope) { intermediateWriter.Write($"base[{metadata.ParameterNames}] = value;"); } } } intermediateWriter.Write($"{metadata.Access} override {metadata.ReturnType} this[{metadata.ParameterTypesAndNames}]"); using (intermediateWriter.Scope) { if (info.CanRead) { intermediateWriter.Write($"get {{ return (({stubTypeName})this).get_Item({metadata.ParameterNames}); }}"); } if (info.CanWrite) { intermediateWriter.Write($"set {{ (({stubTypeName})this).set_Item({metadata.ParameterNames}, value); }}"); } } }
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;"); } } }
private static void ImplementEvent(MemberInfo info, MemberMetadata metadata, IPatternBuilder builder) { var eventInfo = (EventInfo)info; builder.AppendEvent(eventInfo, metadata); }
private void ImplementInterfaceMethod(MethodInfo info, string localImplementationName, MemberMetadata method) { var call = $"this.{localImplementationName}"; writer.Write($"{method.ReturnType} {method.DeclaringType}.{method.Name}({method.ParameterTypesAndNames})"); using (writer.Scope) { writer.AssignDefaultValuesToOutParameters(info.DeclaringType.Namespace, info.GetParameters()); writer.Write($"if ({call} != null)"); using (writer.Scope) { writer.Write($"{method.ReturnClause}{call}({method.ParameterNames});"); } if (method.ReturnType != "void") { writer.Write("else"); using (writer.Scope) { writer.Write($"return default({method.ReturnType});"); } } } }