788 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			C#
		
	
	
			
		
		
	
	
			788 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			C#
		
	
	
using Google.Protobuf.Reflection;
 | 
						|
using System;
 | 
						|
using System.Collections.Generic;
 | 
						|
using System.IO;
 | 
						|
using System.Linq;
 | 
						|
using System.Text;
 | 
						|
 | 
						|
namespace ProtoBuf.Reflection
 | 
						|
{
 | 
						|
    /// <summary>
 | 
						|
    /// A coded generator that writes C#
 | 
						|
    /// </summary>
 | 
						|
    public class CSharpCodeGenerator : CommonCodeGenerator
 | 
						|
    {
 | 
						|
        /// <summary>
 | 
						|
        /// Reusable code-generator instance
 | 
						|
        /// </summary>
 | 
						|
        public static CSharpCodeGenerator Default { get; } = new CSharpCodeGenerator();
 | 
						|
        /// <summary>
 | 
						|
        /// Create a new CSharpCodeGenerator instance
 | 
						|
        /// </summary>
 | 
						|
        protected CSharpCodeGenerator() { }
 | 
						|
        /// <summary>
 | 
						|
        /// Returns the language name
 | 
						|
        /// </summary>
 | 
						|
        public override string Name => "C#";
 | 
						|
        /// <summary>
 | 
						|
        /// Returns the default file extension
 | 
						|
        /// </summary>
 | 
						|
        protected override string DefaultFileExtension => "cs";
 | 
						|
        /// <summary>
 | 
						|
        /// Escapes language keywords
 | 
						|
        /// </summary>
 | 
						|
        protected override string Escape(string identifier)
 | 
						|
        {
 | 
						|
            switch (identifier)
 | 
						|
            {
 | 
						|
                case "abstract":
 | 
						|
                case "event":
 | 
						|
                case "new":
 | 
						|
                case "struct":
 | 
						|
                case "as":
 | 
						|
                case "explicit":
 | 
						|
                case "null":
 | 
						|
                case "switch":
 | 
						|
                case "base":
 | 
						|
                case "extern":
 | 
						|
                case "object":
 | 
						|
                case "this":
 | 
						|
                case "bool":
 | 
						|
                case "false":
 | 
						|
                case "operator":
 | 
						|
                case "throw":
 | 
						|
                case "break":
 | 
						|
                case "finally":
 | 
						|
                case "out":
 | 
						|
                case "true":
 | 
						|
                case "byte":
 | 
						|
                case "fixed":
 | 
						|
                case "override":
 | 
						|
                case "try":
 | 
						|
                case "case":
 | 
						|
                case "float":
 | 
						|
                case "params":
 | 
						|
                case "typeof":
 | 
						|
                case "catch":
 | 
						|
                case "for":
 | 
						|
                case "private":
 | 
						|
                case "uint":
 | 
						|
                case "char":
 | 
						|
                case "foreach":
 | 
						|
                case "protected":
 | 
						|
                case "ulong":
 | 
						|
                case "checked":
 | 
						|
                case "goto":
 | 
						|
                case "public":
 | 
						|
                case "unchecked":
 | 
						|
                case "class":
 | 
						|
                case "if":
 | 
						|
                case "readonly":
 | 
						|
                case "unsafe":
 | 
						|
                case "const":
 | 
						|
                case "implicit":
 | 
						|
                case "ref":
 | 
						|
                case "ushort":
 | 
						|
                case "continue":
 | 
						|
                case "in":
 | 
						|
                case "return":
 | 
						|
                case "using":
 | 
						|
                case "decimal":
 | 
						|
                case "int":
 | 
						|
                case "sbyte":
 | 
						|
                case "virtual":
 | 
						|
                case "default":
 | 
						|
                case "interface":
 | 
						|
                case "sealed":
 | 
						|
                case "volatile":
 | 
						|
                case "delegate":
 | 
						|
                case "internal":
 | 
						|
                case "short":
 | 
						|
                case "void":
 | 
						|
                case "do":
 | 
						|
                case "is":
 | 
						|
                case "sizeof":
 | 
						|
                case "while":
 | 
						|
                case "double":
 | 
						|
                case "lock":
 | 
						|
                case "stackalloc":
 | 
						|
                case "else":
 | 
						|
                case "long":
 | 
						|
                case "static":
 | 
						|
                case "enum":
 | 
						|
                case "namespace":
 | 
						|
                case "string":
 | 
						|
                    return "@" + identifier;
 | 
						|
                default:
 | 
						|
                    return identifier;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        /// <summary>
 | 
						|
        /// Start a file
 | 
						|
        /// </summary>
 | 
						|
        protected override void WriteFileHeader(GeneratorContext ctx, FileDescriptorProto file, ref object state)
 | 
						|
        {
 | 
						|
            ctx.WriteLine("// This file was generated by a tool; you should avoid making direct changes.")
 | 
						|
               .WriteLine("// Consider using 'partial classes' to extend these types")
 | 
						|
               .WriteLine($"// Input: {Path.GetFileName(ctx.File.Name)}").WriteLine()
 | 
						|
               .WriteLine("#pragma warning disable CS1591, CS0612, CS3021").WriteLine();
 | 
						|
 | 
						|
 | 
						|
            var @namespace = ctx.NameNormalizer.GetName(file);
 | 
						|
 | 
						|
            if (!string.IsNullOrWhiteSpace(@namespace))
 | 
						|
            {
 | 
						|
                state = @namespace;
 | 
						|
                ctx.WriteLine($"namespace {@namespace}");
 | 
						|
                ctx.WriteLine("{").Indent().WriteLine();
 | 
						|
            }
 | 
						|
 | 
						|
        }
 | 
						|
        /// <summary>
 | 
						|
        /// End a file
 | 
						|
        /// </summary>
 | 
						|
        protected override void WriteFileFooter(GeneratorContext ctx, FileDescriptorProto file, ref object state)
 | 
						|
        {
 | 
						|
            var @namespace = (string)state;
 | 
						|
            if (!string.IsNullOrWhiteSpace(@namespace))
 | 
						|
            {
 | 
						|
                ctx.Outdent().WriteLine("}").WriteLine();
 | 
						|
            }
 | 
						|
 | 
						|
            ctx.WriteLine("#pragma warning restore CS1591, CS0612, CS3021");
 | 
						|
        }
 | 
						|
        /// <summary>
 | 
						|
        /// Start an enum
 | 
						|
        /// </summary>
 | 
						|
        protected override void WriteEnumHeader(GeneratorContext ctx, EnumDescriptorProto obj, ref object state)
 | 
						|
        {
 | 
						|
            var name = ctx.NameNormalizer.GetName(obj);
 | 
						|
            var tw = ctx.Write($@"[global::ProtoBuf.ProtoContract(");
 | 
						|
            if (name != obj.Name) tw.Write($@"Name = @""{obj.Name}""");
 | 
						|
            tw.WriteLine(")]");
 | 
						|
            WriteOptions(ctx, obj.Options);
 | 
						|
            ctx.WriteLine($"{GetAccess(GetAccess(obj))} enum {Escape(name)}").WriteLine("{").Indent();
 | 
						|
        }
 | 
						|
        /// <summary>
 | 
						|
        /// End an enum
 | 
						|
        /// </summary>
 | 
						|
 | 
						|
        protected override void WriteEnumFooter(GeneratorContext ctx, EnumDescriptorProto obj, ref object state)
 | 
						|
        {
 | 
						|
            ctx.Outdent().WriteLine("}").WriteLine();
 | 
						|
        }
 | 
						|
        /// <summary>
 | 
						|
        /// Write an enum value
 | 
						|
        /// </summary>
 | 
						|
        protected override void WriteEnumValue(GeneratorContext ctx, EnumValueDescriptorProto obj, ref object state)
 | 
						|
        {
 | 
						|
            var name = ctx.NameNormalizer.GetName(obj);
 | 
						|
            if (name != obj.Name)
 | 
						|
            {
 | 
						|
                var tw = ctx.Write($@"[global::ProtoBuf.ProtoEnum(");
 | 
						|
                tw.Write($@"Name = @""{obj.Name}""");
 | 
						|
                tw.WriteLine(")]");
 | 
						|
            }
 | 
						|
            
 | 
						|
            WriteOptions(ctx, obj.Options);
 | 
						|
            ctx.WriteLine($"{Escape(name)} = {obj.Number},");
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// End a message
 | 
						|
        /// </summary>
 | 
						|
        protected override void WriteMessageFooter(GeneratorContext ctx, DescriptorProto obj, ref object state)
 | 
						|
        {
 | 
						|
            ctx.Outdent().WriteLine("}").WriteLine();
 | 
						|
        }
 | 
						|
        /// <summary>
 | 
						|
        /// Start a message
 | 
						|
        /// </summary>
 | 
						|
        protected override void WriteMessageHeader(GeneratorContext ctx, DescriptorProto obj, ref object state)
 | 
						|
        {
 | 
						|
            var name = ctx.NameNormalizer.GetName(obj);
 | 
						|
            GetTypeName2(obj.FullyQualifiedName);
 | 
						|
            var tw = ctx.Write($@"[global::ProtoBuf.ProtoContract(");
 | 
						|
            if (name != obj.Name) tw.Write($@"Name = @""{obj.Name}""");
 | 
						|
            tw.WriteLine(")]");
 | 
						|
            WriteOptions(ctx, obj.Options);
 | 
						|
            tw = ctx.Write($"{GetAccess(GetAccess(obj))} partial class {Escape(name)}");
 | 
						|
            if (obj.ExtensionRanges.Count != 0) tw.Write(" : global::ProtoBuf.IExtensible");
 | 
						|
            tw.WriteLine();
 | 
						|
            ctx.WriteLine("{").Indent();
 | 
						|
            if (obj.Options?.MessageSetWireFormat == true)
 | 
						|
            {
 | 
						|
                ctx.WriteLine("#error message_set_wire_format is not currently implemented").WriteLine();
 | 
						|
            }
 | 
						|
            if (obj.ExtensionRanges.Count != 0)
 | 
						|
            {
 | 
						|
                ctx.WriteLine($"private global::ProtoBuf.IExtension {FieldPrefix}extensionData;")
 | 
						|
                    .WriteLine($"global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)").Indent()
 | 
						|
                    .WriteLine($"=> global::ProtoBuf.Extensible.GetExtensionObject(ref {FieldPrefix}extensionData, createIfMissing);").Outdent().WriteLine();
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        private static void WriteOptions<T>(GeneratorContext ctx, T obj) where T : class, ISchemaOptions
 | 
						|
        {
 | 
						|
            if (obj == null) return;
 | 
						|
            if (obj.Deprecated)
 | 
						|
            {
 | 
						|
                ctx.WriteLine($"[global::System.Obsolete]");
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        const string FieldPrefix = "__pbn__";
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Get the language specific keyword representing an access level
 | 
						|
        /// </summary>
 | 
						|
        public override string GetAccess(Access access)
 | 
						|
        {
 | 
						|
            switch (access)
 | 
						|
            {
 | 
						|
                case Access.Internal: return "internal";
 | 
						|
                case Access.Public: return "public";
 | 
						|
                case Access.Private: return "private";
 | 
						|
                default: return base.GetAccess(access);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        static HashSet<string> TypeNames2 = new HashSet<string>();
 | 
						|
        static string GetTypeName2(string type) {
 | 
						|
            if (type.StartsWith(".")) { type = type.Substring(1); }
 | 
						|
            TypeNames2.Add(type);
 | 
						|
            return type;
 | 
						|
        }
 | 
						|
		public static void ClearTypeNames(){
 | 
						|
			TypeNames2.Clear ();
 | 
						|
		}
 | 
						|
        /// <summary>
 | 
						|
        /// Write a field
 | 
						|
        /// </summary>
 | 
						|
        protected override void WriteField(GeneratorContext ctx, FieldDescriptorProto obj, ref object state, OneOfStub[] oneOfs)
 | 
						|
        {
 | 
						|
            string dataFormat;
 | 
						|
            var typeName = GetTypeName(ctx, obj, out dataFormat, out var isMap);
 | 
						|
            if (isMap)
 | 
						|
            {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            var name = ctx.NameNormalizer.GetName(obj);
 | 
						|
            var tw = ctx.Write($@"[global::ProtoBuf.ProtoMember({obj.Number}");
 | 
						|
 | 
						|
            if (!string.IsNullOrWhiteSpace(dataFormat))
 | 
						|
            {
 | 
						|
                tw.Write($", (int)global::ProtoBuf.DataFormat.{dataFormat}");
 | 
						|
            }
 | 
						|
            if (name != obj.Name)
 | 
						|
            {
 | 
						|
                tw.Write($@", Name = @""{obj.Name}""");
 | 
						|
            }
 | 
						|
            var options = obj.Options?.GetOptions();
 | 
						|
            if (options?.AsReference == true)
 | 
						|
            {
 | 
						|
                tw.Write($@", AsReference = true");
 | 
						|
            }
 | 
						|
            if (options?.DynamicType == true)
 | 
						|
            {
 | 
						|
                tw.Write($@", DynamicType = true");
 | 
						|
            }
 | 
						|
 | 
						|
            bool isOptional = obj.label == FieldDescriptorProto.Label.LabelOptional;
 | 
						|
            bool isRepeated = obj.label == FieldDescriptorProto.Label.LabelRepeated;
 | 
						|
            // Only needed by ILRuntime
 | 
						|
            /*if (isRepeated && obj.type == FieldDescriptorProto.Type.TypeMessage)
 | 
						|
            {
 | 
						|
                tw.Write($@", TypeName = ""{GetTypeName2(obj.TypeName)}""");
 | 
						|
            }*/
 | 
						|
            OneOfStub oneOf = obj.ShouldSerializeOneofIndex() ? oneOfs?[obj.OneofIndex] : null;
 | 
						|
            if (oneOf != null && oneOf.CountTotal == 1)
 | 
						|
            {
 | 
						|
                oneOf = null; // not really a one-of, then!
 | 
						|
            }
 | 
						|
            bool explicitValues = isOptional && oneOf == null && ctx.Syntax == FileDescriptorProto.SyntaxProto2
 | 
						|
                && obj.type != FieldDescriptorProto.Type.TypeMessage
 | 
						|
                && obj.type != FieldDescriptorProto.Type.TypeGroup;
 | 
						|
 | 
						|
 | 
						|
            string defaultValue = null;
 | 
						|
            bool suppressDefaultAttribute = !isOptional;
 | 
						|
            if (isOptional || obj.type == FieldDescriptorProto.Type.TypeEnum)
 | 
						|
            {
 | 
						|
				//GetTypeName2(obj.TypeName);
 | 
						|
                defaultValue = obj.DefaultValue;
 | 
						|
 | 
						|
                if (obj.type == FieldDescriptorProto.Type.TypeString)
 | 
						|
                {
 | 
						|
                    defaultValue = string.IsNullOrEmpty(defaultValue) ? "\"\""
 | 
						|
                        : ("@\"" + (defaultValue ?? "").Replace("\"", "\"\"") + "\"");
 | 
						|
                }
 | 
						|
                else if (obj.type == FieldDescriptorProto.Type.TypeDouble)
 | 
						|
                {
 | 
						|
                    switch (defaultValue)
 | 
						|
                    {
 | 
						|
                        case "inf": defaultValue = "double.PositiveInfinity"; break;
 | 
						|
                        case "-inf": defaultValue = "double.NegativeInfinity"; break;
 | 
						|
                        case "nan": defaultValue = "double.NaN"; break;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                else if (obj.type == FieldDescriptorProto.Type.TypeFloat)
 | 
						|
                {
 | 
						|
                    switch (defaultValue)
 | 
						|
                    {
 | 
						|
                        case "inf": defaultValue = "float.PositiveInfinity"; break;
 | 
						|
                        case "-inf": defaultValue = "float.NegativeInfinity"; break;
 | 
						|
                        case "nan": defaultValue = "float.NaN"; break;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                else if (obj.type == FieldDescriptorProto.Type.TypeEnum)
 | 
						|
                {
 | 
						|
                    var enumType = ctx.TryFind<EnumDescriptorProto>(obj.TypeName);
 | 
						|
                    if (enumType != null)
 | 
						|
                    {
 | 
						|
                        EnumValueDescriptorProto found = null;
 | 
						|
                        if (!string.IsNullOrEmpty(defaultValue))
 | 
						|
                        {
 | 
						|
                            found = enumType.Values.FirstOrDefault(x => x.Name == defaultValue);
 | 
						|
                        }
 | 
						|
                        else if (ctx.Syntax == FileDescriptorProto.SyntaxProto2)
 | 
						|
                        {
 | 
						|
                            // find the first one; if that is a zero, we don't need it after all
 | 
						|
                            found = enumType.Values.FirstOrDefault();
 | 
						|
                            if(found != null && found.Number == 0)
 | 
						|
                            {
 | 
						|
                                if(!isOptional) found = null; // we don't need it after all
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                        // for proto3 the default is 0, so no need to do anything - GetValueOrDefault() will do it all
 | 
						|
 | 
						|
                        if (found != null)
 | 
						|
                        {
 | 
						|
                            defaultValue = ctx.NameNormalizer.GetName(found);
 | 
						|
                        }
 | 
						|
                        if (!string.IsNullOrWhiteSpace(defaultValue))
 | 
						|
                        {
 | 
						|
                            //defaultValue = ctx.NameNormalizer.GetName(enumType) + "." + defaultValue;
 | 
						|
 | 
						|
							defaultValue = "global::"+enumType.FullyQualifiedName.Substring(1) + "." + defaultValue;
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            
 | 
						|
            if (obj.IsPacked(ctx.Syntax))
 | 
						|
            {
 | 
						|
                tw.Write($", IsPacked = true");
 | 
						|
            }
 | 
						|
            if (obj.label == FieldDescriptorProto.Label.LabelRequired)
 | 
						|
            {
 | 
						|
                tw.Write($", IsRequired = true");
 | 
						|
            }
 | 
						|
            tw.WriteLine(")]");
 | 
						|
            if (!isRepeated && !string.IsNullOrWhiteSpace(defaultValue) && !suppressDefaultAttribute)
 | 
						|
            {
 | 
						|
                ctx.WriteLine($"[global::System.ComponentModel.DefaultValue({defaultValue})]");
 | 
						|
            }
 | 
						|
            WriteOptions(ctx, obj.Options);
 | 
						|
            if (isRepeated)
 | 
						|
            {
 | 
						|
                var mapMsgType = isMap ? ctx.TryFind<DescriptorProto>(obj.TypeName) : null;
 | 
						|
                if (mapMsgType != null)
 | 
						|
                {
 | 
						|
                    string keyDataFormat;
 | 
						|
                    bool _;
 | 
						|
                    var keyTypeName = GetTypeName(ctx, mapMsgType.Fields.Single(x => x.Number == 1),
 | 
						|
                        out keyDataFormat, out _);
 | 
						|
                    string valueDataFormat;
 | 
						|
                    var valueTypeName = GetTypeName(ctx, mapMsgType.Fields.Single(x => x.Number == 2),
 | 
						|
                        out valueDataFormat, out _);
 | 
						|
 | 
						|
                    bool first = true;
 | 
						|
                    tw = ctx.Write($"[global::ProtoBuf.ProtoMap");
 | 
						|
                    if (!string.IsNullOrWhiteSpace(keyDataFormat))
 | 
						|
                    {
 | 
						|
                        tw.Write($"{(first ? "(" : ", ")}KeyFormat = global::ProtoBuf.DataFormat.{keyDataFormat}");
 | 
						|
                        first = false;
 | 
						|
                    }
 | 
						|
                    if (!string.IsNullOrWhiteSpace(valueDataFormat))
 | 
						|
                    {
 | 
						|
                        tw.Write($"{(first ? "(" : ", ")}ValueFormat = global::ProtoBuf.DataFormat.{valueDataFormat}");
 | 
						|
                        first = false;
 | 
						|
                    }
 | 
						|
                    tw.WriteLine(first ? "]" : ")]");
 | 
						|
                    ctx.WriteLine($"{GetAccess(GetAccess(obj))} global::System.Collections.Generic.Dictionary<{keyTypeName}, {valueTypeName}> {Escape(name)} {{ get; }} = new global::System.Collections.Generic.Dictionary<{keyTypeName}, {valueTypeName}>();");
 | 
						|
                }
 | 
						|
                else if (UseArray(obj))
 | 
						|
                {
 | 
						|
                    ctx.WriteLine($"{GetAccess(GetAccess(obj))} {typeName}[] {Escape(name)} {{ get; set; }}");
 | 
						|
                }
 | 
						|
                else
 | 
						|
                {
 | 
						|
                    ctx.WriteLine($"{GetAccess(GetAccess(obj))} global::System.Collections.Generic.List<{typeName}> {Escape(name)} {{ get; }} = new global::System.Collections.Generic.List<{typeName}>();");
 | 
						|
                }
 | 
						|
            }
 | 
						|
            else if (oneOf != null)
 | 
						|
            {
 | 
						|
                var defValue = string.IsNullOrWhiteSpace(defaultValue) ? $"default({typeName})" : defaultValue;
 | 
						|
                var fieldName = FieldPrefix + oneOf.OneOf.Name;
 | 
						|
                var storage = oneOf.GetStorage(obj.type, obj.TypeName);
 | 
						|
                ctx.WriteLine($"{GetAccess(GetAccess(obj))} {typeName} {Escape(name)}").WriteLine("{").Indent();
 | 
						|
 | 
						|
                switch (obj.type)
 | 
						|
                {
 | 
						|
                    case FieldDescriptorProto.Type.TypeMessage:
 | 
						|
                    case FieldDescriptorProto.Type.TypeGroup:
 | 
						|
                    case FieldDescriptorProto.Type.TypeEnum:
 | 
						|
                    case FieldDescriptorProto.Type.TypeBytes:
 | 
						|
                    case FieldDescriptorProto.Type.TypeString:
 | 
						|
                        ctx.WriteLine($"get {{ return {fieldName}.Is({obj.Number}) ? (({typeName}){fieldName}.{storage}) : {defValue}; }}");
 | 
						|
                        break;
 | 
						|
                    default:
 | 
						|
                        ctx.WriteLine($"get {{ return {fieldName}.Is({obj.Number}) ? {fieldName}.{storage} : {defValue}; }}");
 | 
						|
                        break;
 | 
						|
                }
 | 
						|
                var unionType = oneOf.GetUnionType();
 | 
						|
                ctx.WriteLine($"set {{ {fieldName} = new global::ProtoBuf.{unionType}({obj.Number}, value); }}")
 | 
						|
                    .Outdent().WriteLine("}")
 | 
						|
                    .WriteLine($"{GetAccess(GetAccess(obj))} bool ShouldSerialize{name}() => {fieldName}.Is({obj.Number});")
 | 
						|
                    .WriteLine($"{GetAccess(GetAccess(obj))} void Reset{name}() => global::ProtoBuf.{unionType}.Reset(ref {fieldName}, {obj.Number});");
 | 
						|
 | 
						|
                if (oneOf.IsFirst())
 | 
						|
                {
 | 
						|
                    ctx.WriteLine().WriteLine($"private global::ProtoBuf.{unionType} {fieldName};");
 | 
						|
                }
 | 
						|
            }
 | 
						|
            else if (explicitValues)
 | 
						|
            {
 | 
						|
                string fieldName = FieldPrefix + name, fieldType;
 | 
						|
                bool isRef = false;
 | 
						|
                switch (obj.type)
 | 
						|
                {
 | 
						|
                    case FieldDescriptorProto.Type.TypeString:
 | 
						|
                    case FieldDescriptorProto.Type.TypeBytes:
 | 
						|
                        fieldType = typeName;
 | 
						|
                        isRef = true;
 | 
						|
                        break;
 | 
						|
                    default:
 | 
						|
                        fieldType = typeName + "?";
 | 
						|
                        break;
 | 
						|
                }
 | 
						|
                ctx.WriteLine($"{GetAccess(GetAccess(obj))} {typeName} {Escape(name)}").WriteLine("{").Indent();
 | 
						|
                tw = ctx.Write($"get {{ return {fieldName}");
 | 
						|
                if (!string.IsNullOrWhiteSpace(defaultValue))
 | 
						|
                {
 | 
						|
                    tw.Write(" ?? ");
 | 
						|
                    tw.Write(defaultValue);
 | 
						|
                }
 | 
						|
                else if (!isRef)
 | 
						|
                {
 | 
						|
                    tw.Write(".GetValueOrDefault()");
 | 
						|
                }
 | 
						|
                tw.WriteLine("; }");
 | 
						|
                ctx.WriteLine($"set {{ {fieldName} = value; }}")
 | 
						|
                    .Outdent().WriteLine("}")
 | 
						|
                    .WriteLine($"{GetAccess(GetAccess(obj))} bool ShouldSerialize{name}() => {fieldName} != null;")
 | 
						|
                    .WriteLine($"{GetAccess(GetAccess(obj))} void Reset{name}() => {fieldName} = null;")
 | 
						|
                    .WriteLine($"private {fieldType} {fieldName};");
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                tw = ctx.Write($"{GetAccess(GetAccess(obj))} {typeName} {Escape(name)} {{ get; set; }}");
 | 
						|
                if (!string.IsNullOrWhiteSpace(defaultValue)) tw.Write($" = {defaultValue};");
 | 
						|
                tw.WriteLine();
 | 
						|
            }
 | 
						|
            ctx.WriteLine();
 | 
						|
        }
 | 
						|
        /// <summary>
 | 
						|
        /// Starts an extgensions block
 | 
						|
        /// </summary>
 | 
						|
        protected override void WriteExtensionsHeader(GeneratorContext ctx, FileDescriptorProto obj, ref object state)
 | 
						|
        {
 | 
						|
            var name = obj?.Options?.GetOptions()?.ExtensionTypeName;
 | 
						|
            if (string.IsNullOrWhiteSpace(name)) name = "Extensions";
 | 
						|
            ctx.WriteLine($"{GetAccess(GetAccess(obj))} static class {Escape(name)}").WriteLine("{").Indent();
 | 
						|
        }
 | 
						|
        /// <summary>
 | 
						|
        /// Ends an extgensions block
 | 
						|
        /// </summary>
 | 
						|
        protected override void WriteExtensionsFooter(GeneratorContext ctx, FileDescriptorProto obj, ref object state)
 | 
						|
        {
 | 
						|
            ctx.Outdent().WriteLine("}");
 | 
						|
        }
 | 
						|
        /// <summary>
 | 
						|
        /// Starts an extensions block
 | 
						|
        /// </summary>
 | 
						|
        protected override void WriteExtensionsHeader(GeneratorContext ctx, DescriptorProto obj, ref object state)
 | 
						|
        {
 | 
						|
            var name = obj?.Options?.GetOptions()?.ExtensionTypeName;
 | 
						|
            if (string.IsNullOrWhiteSpace(name)) name = "Extensions";
 | 
						|
            ctx.WriteLine($"{GetAccess(GetAccess(obj))} static class {Escape(name)}").WriteLine("{").Indent();
 | 
						|
        }
 | 
						|
        /// <summary>
 | 
						|
        /// Ends an extensions block
 | 
						|
        /// </summary>
 | 
						|
        protected override void WriteExtensionsFooter(GeneratorContext ctx, DescriptorProto obj, ref object state)
 | 
						|
        {
 | 
						|
            ctx.Outdent().WriteLine("}");
 | 
						|
        }
 | 
						|
        /// <summary>
 | 
						|
        /// Write an extension
 | 
						|
        /// </summary>
 | 
						|
        protected override void WriteExtension(GeneratorContext ctx, FieldDescriptorProto field)
 | 
						|
        {
 | 
						|
            string dataFormat;
 | 
						|
            bool isMap;
 | 
						|
            var type = GetTypeName(ctx, field, out dataFormat, out isMap);
 | 
						|
 | 
						|
            if (isMap)
 | 
						|
            {
 | 
						|
                ctx.WriteLine("#error map extensions not yet implemented");
 | 
						|
            }
 | 
						|
            else if (field.label == FieldDescriptorProto.Label.LabelRepeated)
 | 
						|
            {
 | 
						|
                ctx.WriteLine("#error repeated extensions not yet implemented");
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                var msg = ctx.TryFind<DescriptorProto>(field.Extendee);
 | 
						|
                var extendee = MakeRelativeName(field, msg, ctx.NameNormalizer);
 | 
						|
 | 
						|
                var @this = field.Parent is FileDescriptorProto ? "this " : "";
 | 
						|
                string name = ctx.NameNormalizer.GetName(field);
 | 
						|
                var tw = ctx.WriteLine($"{GetAccess(GetAccess(field))} static {type} Get{name}({@this}{extendee} obj)")
 | 
						|
                    .Write($"=> obj == null ? default({type}) : global::ProtoBuf.Extensible.GetValue<{type}>(obj, {field.Number}");
 | 
						|
                if (!string.IsNullOrEmpty(dataFormat))
 | 
						|
                {
 | 
						|
                    tw.Write($", global::ProtoBuf.DataFormat.{dataFormat}");
 | 
						|
                }
 | 
						|
                tw.WriteLine(");");
 | 
						|
                ctx.WriteLine();
 | 
						|
                //  GetValue<TValue>(IExtensible instance, int tag, DataFormat format)
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        private static bool UseArray(FieldDescriptorProto field)
 | 
						|
        {
 | 
						|
            switch (field.type)
 | 
						|
            {
 | 
						|
                case FieldDescriptorProto.Type.TypeBool:
 | 
						|
                case FieldDescriptorProto.Type.TypeDouble:
 | 
						|
                case FieldDescriptorProto.Type.TypeFixed32:
 | 
						|
                case FieldDescriptorProto.Type.TypeFixed64:
 | 
						|
                case FieldDescriptorProto.Type.TypeFloat:
 | 
						|
                case FieldDescriptorProto.Type.TypeInt32:
 | 
						|
                case FieldDescriptorProto.Type.TypeInt64:
 | 
						|
                case FieldDescriptorProto.Type.TypeSfixed32:
 | 
						|
                case FieldDescriptorProto.Type.TypeSfixed64:
 | 
						|
                case FieldDescriptorProto.Type.TypeSint32:
 | 
						|
                case FieldDescriptorProto.Type.TypeSint64:
 | 
						|
                case FieldDescriptorProto.Type.TypeUint32:
 | 
						|
                case FieldDescriptorProto.Type.TypeUint64:
 | 
						|
                    return true;
 | 
						|
                default:
 | 
						|
                    return false;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        private string GetTypeName(GeneratorContext ctx, FieldDescriptorProto field, out string dataFormat, out bool isMap)
 | 
						|
        {
 | 
						|
            dataFormat = "";
 | 
						|
            isMap = false;
 | 
						|
            switch (field.type)
 | 
						|
            {
 | 
						|
                case FieldDescriptorProto.Type.TypeDouble:
 | 
						|
                    return "double";
 | 
						|
                case FieldDescriptorProto.Type.TypeFloat:
 | 
						|
                    return "float";
 | 
						|
                case FieldDescriptorProto.Type.TypeBool:
 | 
						|
                    return "bool";
 | 
						|
                case FieldDescriptorProto.Type.TypeString:
 | 
						|
                    return "string";
 | 
						|
                case FieldDescriptorProto.Type.TypeSint32:
 | 
						|
                    dataFormat = nameof(DataFormat.ZigZag);
 | 
						|
                    return "int";
 | 
						|
                case FieldDescriptorProto.Type.TypeInt32:
 | 
						|
                    return "int";
 | 
						|
                case FieldDescriptorProto.Type.TypeSfixed32:
 | 
						|
                    dataFormat = nameof(DataFormat.FixedSize);
 | 
						|
                    return "int";
 | 
						|
                case FieldDescriptorProto.Type.TypeSint64:
 | 
						|
                    dataFormat = nameof(DataFormat.ZigZag);
 | 
						|
                    return "long";
 | 
						|
                case FieldDescriptorProto.Type.TypeInt64:
 | 
						|
                    return "long";
 | 
						|
                case FieldDescriptorProto.Type.TypeSfixed64:
 | 
						|
                    dataFormat = nameof(DataFormat.FixedSize);
 | 
						|
                    return "long";
 | 
						|
                case FieldDescriptorProto.Type.TypeFixed32:
 | 
						|
                    dataFormat = nameof(DataFormat.FixedSize);
 | 
						|
                    return "uint";
 | 
						|
                case FieldDescriptorProto.Type.TypeUint32:
 | 
						|
                    return "uint";
 | 
						|
                case FieldDescriptorProto.Type.TypeFixed64:
 | 
						|
                    dataFormat = nameof(DataFormat.FixedSize);
 | 
						|
                    return "ulong";
 | 
						|
                case FieldDescriptorProto.Type.TypeUint64:
 | 
						|
                    return "ulong";
 | 
						|
                case FieldDescriptorProto.Type.TypeBytes:
 | 
						|
                    return "byte[]";
 | 
						|
                case FieldDescriptorProto.Type.TypeEnum:
 | 
						|
                    switch (field.TypeName)
 | 
						|
                    {
 | 
						|
                        case ".bcl.DateTime.DateTimeKind":
 | 
						|
                            return "global::System.DateTimeKind";
 | 
						|
                    }
 | 
						|
                    var enumType = ctx.TryFind<EnumDescriptorProto>(field.TypeName);
 | 
						|
                    return MakeRelativeName(field, enumType, ctx.NameNormalizer);
 | 
						|
                case FieldDescriptorProto.Type.TypeGroup:
 | 
						|
                case FieldDescriptorProto.Type.TypeMessage:
 | 
						|
                    switch (field.TypeName)
 | 
						|
                    {
 | 
						|
                        case WellKnownTypeTimestamp:
 | 
						|
                            dataFormat = "WellKnown";
 | 
						|
                            return "global::System.DateTime?";
 | 
						|
                        case WellKnownTypeDuration:
 | 
						|
                            dataFormat = "WellKnown";
 | 
						|
                            return "global::System.TimeSpan?";
 | 
						|
                        case ".bcl.NetObjectProxy":
 | 
						|
                            return "object";
 | 
						|
                        case ".bcl.DateTime":
 | 
						|
                            return "global::System.DateTime?";
 | 
						|
                        case ".bcl.TimeSpan":
 | 
						|
                            return "global::System.TimeSpan?";
 | 
						|
                        case ".bcl.Decimal":
 | 
						|
                            return "decimal?";
 | 
						|
                        case ".bcl.Guid":
 | 
						|
                            return "global::System.Guid?";
 | 
						|
                    }
 | 
						|
                    var msgType = ctx.TryFind<DescriptorProto>(field.TypeName);
 | 
						|
                    if (field.type == FieldDescriptorProto.Type.TypeGroup)
 | 
						|
                    {
 | 
						|
                        dataFormat = nameof(DataFormat.Group);
 | 
						|
                    }
 | 
						|
                    isMap = msgType?.Options?.MapEntry ?? false;
 | 
						|
                    return MakeRelativeName(field, msgType, ctx.NameNormalizer);
 | 
						|
                default:
 | 
						|
                    return field.TypeName;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        private string MakeRelativeName(FieldDescriptorProto field, IType target, NameNormalizer normalizer)
 | 
						|
        {
 | 
						|
            if (target == null) return Escape(field.TypeName); // the only thing we know
 | 
						|
 | 
						|
            var declaringType = field.Parent;
 | 
						|
 | 
						|
            if (declaringType is IType)
 | 
						|
            {
 | 
						|
                var name = FindNameFromCommonAncestor((IType)declaringType, target, normalizer);
 | 
						|
                if (!string.IsNullOrWhiteSpace(name)) return name;
 | 
						|
            }
 | 
						|
            return Escape(field.TypeName); // give up!
 | 
						|
        }
 | 
						|
 | 
						|
        // k, what we do is; we have two types; each knows the parent, but nothing else, so:
 | 
						|
        // for each, use a stack to build the ancestry tree - the "top" of the stack will be the
 | 
						|
        // package, the bottom of the stack will be the type itself. They will often be stacks
 | 
						|
        // of different heights.
 | 
						|
        //
 | 
						|
        // Find how many is in the smallest stack; now take that many items, in turn, until we
 | 
						|
        // get something that is different (at which point, put that one back on the stack), or 
 | 
						|
        // we run out of items in one of the stacks.
 | 
						|
        //
 | 
						|
        // There are now two options:
 | 
						|
        // - we ran out of things in the "target" stack - in which case, they are common enough to not
 | 
						|
        //   need any resolution - just give back the fixed name
 | 
						|
        // - we have things left in the "target" stack - in which case we have found a common ancestor,
 | 
						|
        //   or the target is a descendent; either way, just concat what is left (including the package
 | 
						|
        //   if the package itself was different)
 | 
						|
 | 
						|
        private string FindNameFromCommonAncestor(IType declaring, IType target, NameNormalizer normalizer)
 | 
						|
        {
 | 
						|
            // trivial case; asking for self, or asking for immediate child
 | 
						|
            if (ReferenceEquals(declaring, target) || ReferenceEquals(declaring, target.Parent))
 | 
						|
            {
 | 
						|
                if (target is DescriptorProto) return Escape(normalizer.GetName((DescriptorProto)target));
 | 
						|
                if (target is EnumDescriptorProto) return Escape(normalizer.GetName((EnumDescriptorProto)target));
 | 
						|
                return null;
 | 
						|
            }
 | 
						|
 | 
						|
            var origTarget = target;
 | 
						|
            var xStack = new Stack<IType>();
 | 
						|
 | 
						|
            while (declaring != null)
 | 
						|
            {
 | 
						|
                xStack.Push(declaring);
 | 
						|
                declaring = declaring.Parent;
 | 
						|
            }
 | 
						|
            var yStack = new Stack<IType>();
 | 
						|
 | 
						|
            while (target != null)
 | 
						|
            {
 | 
						|
                yStack.Push(target);
 | 
						|
                target = target.Parent;
 | 
						|
            }
 | 
						|
            int lim = Math.Min(xStack.Count, yStack.Count);
 | 
						|
            for (int i = 0; i < lim; i++)
 | 
						|
            {
 | 
						|
                declaring = xStack.Peek();
 | 
						|
                target = yStack.Pop();
 | 
						|
                if (!ReferenceEquals(target, declaring))
 | 
						|
                {
 | 
						|
                    // special-case: if both are the package (file), and they have the same namespace: we're OK
 | 
						|
                    if (target is FileDescriptorProto && declaring is FileDescriptorProto &&
 | 
						|
                        normalizer.GetName((FileDescriptorProto)declaring) == normalizer.GetName((FileDescriptorProto)target))
 | 
						|
                    {
 | 
						|
                        // that's fine, keep going
 | 
						|
                    }
 | 
						|
                    else
 | 
						|
                    {
 | 
						|
                        // put it back
 | 
						|
                        yStack.Push(target);
 | 
						|
                        break;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            // if we used everything, then the target is an ancestor-or-self
 | 
						|
            if (yStack.Count == 0)
 | 
						|
            {
 | 
						|
                target = origTarget;
 | 
						|
                if (target is DescriptorProto) return Escape(normalizer.GetName((DescriptorProto)target));
 | 
						|
                if (target is EnumDescriptorProto) return Escape(normalizer.GetName((EnumDescriptorProto)target));
 | 
						|
                return null;
 | 
						|
            }
 | 
						|
 | 
						|
            var sb = new StringBuilder();
 | 
						|
            while (yStack.Count != 0)
 | 
						|
            {
 | 
						|
                target = yStack.Pop();
 | 
						|
 | 
						|
                string nextName;
 | 
						|
                if (target is FileDescriptorProto) nextName = normalizer.GetName((FileDescriptorProto)target);
 | 
						|
                else if (target is DescriptorProto) nextName = normalizer.GetName((DescriptorProto)target);
 | 
						|
                else if (target is EnumDescriptorProto) nextName = normalizer.GetName((EnumDescriptorProto)target);
 | 
						|
                else return null;
 | 
						|
 | 
						|
                if (!string.IsNullOrWhiteSpace(nextName))
 | 
						|
                {
 | 
						|
                    if (sb.Length == 0 && target is FileDescriptorProto) sb.Append("global::");
 | 
						|
                    else if (sb.Length != 0) sb.Append('.');
 | 
						|
                    sb.Append(Escape(nextName));
 | 
						|
                }
 | 
						|
            }
 | 
						|
            return sb.ToString();
 | 
						|
        }
 | 
						|
 | 
						|
        static bool IsAncestorOrSelf(IType parent, IType child)
 | 
						|
        {
 | 
						|
            while (parent != null)
 | 
						|
            {
 | 
						|
                if (ReferenceEquals(parent, child)) return true;
 | 
						|
                parent = parent.Parent;
 | 
						|
            }
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
        const string WellKnownTypeTimestamp = ".google.protobuf.Timestamp",
 | 
						|
                     WellKnownTypeDuration = ".google.protobuf.Duration";
 | 
						|
    }
 | 
						|
}
 |