static List <IMember> DetectMemberOrder(ITypeDefinition recordTypeDef, Dictionary <IField, IProperty> backingFieldToAutoProperty) { // For records, the order of members is important: // Equals/GetHashCode/PrintMembers must agree on an order of fields+properties. // The IL metadata has the order of fields and the order of properties, but we // need to detect the correct interleaving. // We could try to detect this from the PrintMembers body, but let's initially // restrict ourselves to the common case where the record only uses properties. var subst = recordTypeDef.AsParameterizedType().GetSubstitution(); return(recordTypeDef.Properties.Select(p => p.Specialize(subst)).Concat( recordTypeDef.Fields.Select(f => (IField)f.Specialize(subst)).Where(f => !backingFieldToAutoProperty.ContainsKey(f)) ).ToList()); }
void DetectAutomaticProperties() { var subst = recordTypeDef.AsParameterizedType().GetSubstitution(); foreach (var property in recordTypeDef.Properties) { cancellationToken.ThrowIfCancellationRequested(); var p = (IProperty)property.Specialize(subst); if (IsAutoProperty(p, out var field)) { backingFieldToAutoProperty.Add(field, p); autoPropertyToBackingField.Add(p, field); } } bool IsAutoProperty(IProperty p, out IField field) { field = null; if (p.Parameters.Count != 0) { return(false); } if (p.Getter != null) { if (!IsAutoGetter(p.Getter, out field)) { return(false); } } if (p.Setter != null) { if (!IsAutoSetter(p.Setter, out var field2)) { return(false); } if (field != null) { if (!field.Equals(field2)) { return(false); } } else { field = field2; } } if (field == null) { return(false); } if (!IsRecordType(field.DeclaringType)) { return(false); } return(field.Name == $"<{p.Name}>k__BackingField"); } bool IsAutoGetter(IMethod method, out IField field) { field = null; var body = DecompileBody(method); if (body == null) { return(false); } // return this.field; if (!body.Instructions[0].MatchReturn(out var retVal)) { return(false); } if (method.IsStatic) { return(retVal.MatchLdsFld(out field)); } else { if (!retVal.MatchLdFld(out var target, out field)) { return(false); } return(target.MatchLdThis()); } } bool IsAutoSetter(IMethod method, out IField field) { field = null; Debug.Assert(!method.IsStatic); var body = DecompileBody(method); if (body == null) { return(false); } // this.field = value; ILInstruction valueInst; if (method.IsStatic) { if (!body.Instructions[0].MatchStsFld(out field, out valueInst)) { return(false); } } else { if (!body.Instructions[0].MatchStFld(out var target, out field, out valueInst)) { return(false); } if (!target.MatchLdThis()) { return(false); } } if (!valueInst.MatchLdLoc(out var value)) { return(false); } if (!(value.Kind == VariableKind.Parameter && value.Index == 0)) { return(false); } return(body.Instructions[1].MatchReturn(out var retVal) && retVal.MatchNop()); } }