The added attribute will enable consuming projects that have Nullable Reference Types enabled to receive proper warnings when access null checks aren't performed.
583 lines
23 KiB
C#
583 lines
23 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using Umbraco.Cms.Core;
|
|
using Umbraco.Cms.Core.Configuration.Models;
|
|
|
|
namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Building
|
|
{
|
|
/// <summary>
|
|
/// Implements a builder that works by writing text.
|
|
/// </summary>
|
|
public class TextBuilder : Builder
|
|
{
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="TextBuilder"/> class with a list of models to generate
|
|
/// and the result of code parsing.
|
|
/// </summary>
|
|
/// <param name="typeModels">The list of models to generate.</param>
|
|
public TextBuilder(ModelsBuilderSettings config, IList<TypeModel> typeModels)
|
|
: base(config, typeModels)
|
|
{ }
|
|
|
|
// internal for unit tests only
|
|
public TextBuilder()
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// Outputs a generated model to a string builder.
|
|
/// </summary>
|
|
/// <param name="sb">The string builder.</param>
|
|
/// <param name="typeModel">The model to generate.</param>
|
|
public void Generate(StringBuilder sb, TypeModel typeModel)
|
|
{
|
|
WriteHeader(sb);
|
|
|
|
foreach (var t in TypesUsing)
|
|
sb.AppendFormat("using {0};\n", t);
|
|
|
|
sb.Append("\n");
|
|
sb.AppendFormat("namespace {0}\n", GetModelsNamespace());
|
|
sb.Append("{\n");
|
|
|
|
WriteContentType(sb, typeModel);
|
|
|
|
sb.Append("}\n");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Outputs generated models to a string builder.
|
|
/// </summary>
|
|
/// <param name="sb">The string builder.</param>
|
|
/// <param name="typeModels">The models to generate.</param>
|
|
public void Generate(StringBuilder sb, IEnumerable<TypeModel> typeModels)
|
|
{
|
|
WriteHeader(sb);
|
|
|
|
foreach (var t in TypesUsing)
|
|
sb.AppendFormat("using {0};\n", t);
|
|
|
|
// assembly attributes marker
|
|
sb.Append("\n//ASSATTR\n");
|
|
|
|
sb.Append("\n");
|
|
sb.AppendFormat("namespace {0}\n", GetModelsNamespace());
|
|
sb.Append("{\n");
|
|
|
|
foreach (var typeModel in typeModels)
|
|
{
|
|
WriteContentType(sb, typeModel);
|
|
sb.Append("\n");
|
|
}
|
|
|
|
sb.Append("}\n");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Outputs an "auto-generated" header to a string builder.
|
|
/// </summary>
|
|
/// <param name="sb">The string builder.</param>
|
|
public static void WriteHeader(StringBuilder sb)
|
|
{
|
|
TextHeaderWriter.WriteHeader(sb);
|
|
}
|
|
|
|
// writes an attribute that identifies code generated by a tool
|
|
// (helps reduce warnings, tools such as FxCop use it)
|
|
// see https://github.com/zpqrtbnk/Zbu.ModelsBuilder/issues/107
|
|
// see https://docs.microsoft.com/en-us/dotnet/api/system.codedom.compiler.generatedcodeattribute
|
|
// see https://blogs.msdn.microsoft.com/codeanalysis/2007/04/27/correct-usage-of-the-compilergeneratedattribute-and-the-generatedcodeattribute/
|
|
//
|
|
// note that the blog post above clearly states that "Nor should it be applied at the type level if the type being generated is a partial class."
|
|
// and since our models are partial classes, we have to apply the attribute against the individual members, not the class itself.
|
|
//
|
|
private static void WriteGeneratedCodeAttribute(StringBuilder sb, string tabs)
|
|
{
|
|
sb.AppendFormat("{0}[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Umbraco.ModelsBuilder.Embedded\", \"{1}\")]\n", tabs, ApiVersion.Current.Version);
|
|
}
|
|
|
|
// writes an attribute that specifies that an output may be null.
|
|
// (useful for consuming projects with nullable reference types enabled)
|
|
private static void WriteMaybeNullAttribute(StringBuilder sb, string tabs, bool isReturn = false)
|
|
{
|
|
sb.AppendFormat("{0}[{1}global::System.Diagnostics.CodeAnalysis.MaybeNull]\n", tabs, isReturn ? "return: " : "");
|
|
}
|
|
|
|
private void WriteContentType(StringBuilder sb, TypeModel type)
|
|
{
|
|
string sep;
|
|
|
|
if (type.IsMixin)
|
|
{
|
|
// write the interface declaration
|
|
sb.AppendFormat("\t// Mixin Content Type with alias \"{0}\"\n", type.Alias);
|
|
if (!string.IsNullOrWhiteSpace(type.Name))
|
|
sb.AppendFormat("\t/// <summary>{0}</summary>\n", XmlCommentString(type.Name));
|
|
sb.AppendFormat("\tpublic partial interface I{0}", type.ClrName);
|
|
var implements = type.BaseType == null
|
|
? (type.HasBase ? null : (type.IsElement ? "PublishedElement" : "PublishedContent"))
|
|
: type.BaseType.ClrName;
|
|
if (implements != null)
|
|
sb.AppendFormat(" : I{0}", implements);
|
|
|
|
// write the mixins
|
|
sep = implements == null ? ":" : ",";
|
|
foreach (var mixinType in type.DeclaringInterfaces.OrderBy(x => x.ClrName))
|
|
{
|
|
sb.AppendFormat("{0} I{1}", sep, mixinType.ClrName);
|
|
sep = ",";
|
|
}
|
|
|
|
sb.Append("\n\t{\n");
|
|
|
|
// write the properties - only the local (non-ignored) ones, we're an interface
|
|
var more = false;
|
|
foreach (var prop in type.Properties.OrderBy(x => x.ClrName))
|
|
{
|
|
if (more) sb.Append("\n");
|
|
more = true;
|
|
WriteInterfaceProperty(sb, prop);
|
|
}
|
|
|
|
sb.Append("\t}\n\n");
|
|
}
|
|
|
|
// write the class declaration
|
|
if (!string.IsNullOrWhiteSpace(type.Name))
|
|
sb.AppendFormat("\t/// <summary>{0}</summary>\n", XmlCommentString(type.Name));
|
|
// cannot do it now. see note in ImplementContentTypeAttribute
|
|
//if (!type.HasImplement)
|
|
// sb.AppendFormat("\t[ImplementContentType(\"{0}\")]\n", type.Alias);
|
|
sb.AppendFormat("\t[PublishedModel(\"{0}\")]\n", type.Alias);
|
|
sb.AppendFormat("\tpublic partial class {0}", type.ClrName);
|
|
var inherits = type.HasBase
|
|
? null // has its own base already
|
|
: (type.BaseType == null
|
|
? GetModelsBaseClassName(type)
|
|
: type.BaseType.ClrName);
|
|
if (inherits != null)
|
|
sb.AppendFormat(" : {0}", inherits);
|
|
|
|
sep = inherits == null ? ":" : ",";
|
|
if (type.IsMixin)
|
|
{
|
|
// if it's a mixin it implements its own interface
|
|
sb.AppendFormat("{0} I{1}", sep, type.ClrName);
|
|
}
|
|
else
|
|
{
|
|
// write the mixins, if any, as interfaces
|
|
// only if not a mixin because otherwise the interface already has them already
|
|
foreach (var mixinType in type.DeclaringInterfaces.OrderBy(x => x.ClrName))
|
|
{
|
|
sb.AppendFormat("{0} I{1}", sep, mixinType.ClrName);
|
|
sep = ",";
|
|
}
|
|
}
|
|
|
|
// begin class body
|
|
sb.Append("\n\t{\n");
|
|
|
|
// write the constants & static methods
|
|
// as 'new' since parent has its own - or maybe not - disable warning
|
|
sb.Append("\t\t// helpers\n");
|
|
sb.Append("#pragma warning disable 0109 // new is redundant\n");
|
|
WriteGeneratedCodeAttribute(sb, "\t\t");
|
|
sb.AppendFormat("\t\tpublic new const string ModelTypeAlias = \"{0}\";\n",
|
|
type.Alias);
|
|
var itemType = type.IsElement ? TypeModel.ItemTypes.Content : type.ItemType; // fixme
|
|
WriteGeneratedCodeAttribute(sb, "\t\t");
|
|
sb.AppendFormat("\t\tpublic new const PublishedItemType ModelItemType = PublishedItemType.{0};\n",
|
|
itemType);
|
|
WriteGeneratedCodeAttribute(sb, "\t\t");
|
|
WriteMaybeNullAttribute(sb, "\t\t", true);
|
|
sb.Append("\t\tpublic new static IPublishedContentType GetModelContentType(IPublishedSnapshotAccessor publishedSnapshotAccessor)\n");
|
|
sb.Append("\t\t\t=> PublishedModelUtility.GetModelContentType(publishedSnapshotAccessor, ModelItemType, ModelTypeAlias);\n");
|
|
WriteGeneratedCodeAttribute(sb, "\t\t");
|
|
WriteMaybeNullAttribute(sb, "\t\t", true);
|
|
sb.AppendFormat("\t\tpublic static IPublishedPropertyType GetModelPropertyType<TValue>(IPublishedSnapshotAccessor publishedSnapshotAccessor, Expression<Func<{0}, TValue>> selector)\n",
|
|
type.ClrName);
|
|
sb.Append("\t\t\t=> PublishedModelUtility.GetModelPropertyType(GetModelContentType(publishedSnapshotAccessor), selector);\n");
|
|
sb.Append("#pragma warning restore 0109\n\n");
|
|
sb.Append("\t\tprivate IPublishedValueFallback _publishedValueFallback;");
|
|
|
|
// write the ctor
|
|
sb.AppendFormat("\n\n\t\t// ctor\n\t\tpublic {0}(IPublished{1} content, IPublishedValueFallback publishedValueFallback)\n\t\t\t: base(content, publishedValueFallback)\n\t\t{{\n\t\t\t_publishedValueFallback = publishedValueFallback;\n\t\t}}\n\n",
|
|
type.ClrName, type.IsElement ? "Element" : "Content");
|
|
|
|
// write the properties
|
|
sb.Append("\t\t// properties\n");
|
|
WriteContentTypeProperties(sb, type);
|
|
|
|
// close the class declaration
|
|
sb.Append("\t}\n");
|
|
}
|
|
|
|
private void WriteContentTypeProperties(StringBuilder sb, TypeModel type)
|
|
{
|
|
var staticMixinGetters = true;
|
|
|
|
// write the properties
|
|
foreach (var prop in type.Properties.OrderBy(x => x.ClrName))
|
|
WriteProperty(sb, type, prop, staticMixinGetters && type.IsMixin ? type.ClrName : null);
|
|
|
|
// no need to write the parent properties since we inherit from the parent
|
|
// and the parent defines its own properties. need to write the mixins properties
|
|
// since the mixins are only interfaces and we have to provide an implementation.
|
|
|
|
// write the mixins properties
|
|
foreach (var mixinType in type.ImplementingInterfaces.OrderBy(x => x.ClrName))
|
|
foreach (var prop in mixinType.Properties.OrderBy(x => x.ClrName))
|
|
if (staticMixinGetters)
|
|
WriteMixinProperty(sb, prop, mixinType.ClrName);
|
|
else
|
|
WriteProperty(sb, mixinType, prop);
|
|
}
|
|
|
|
private void WriteMixinProperty(StringBuilder sb, PropertyModel property, string mixinClrName)
|
|
{
|
|
sb.Append("\n");
|
|
|
|
// Adds xml summary to each property containing
|
|
// property name and property description
|
|
if (!string.IsNullOrWhiteSpace(property.Name) || !string.IsNullOrWhiteSpace(property.Description))
|
|
{
|
|
sb.Append("\t\t///<summary>\n");
|
|
|
|
if (!string.IsNullOrWhiteSpace(property.Description))
|
|
sb.AppendFormat("\t\t/// {0}: {1}\n", XmlCommentString(property.Name), XmlCommentString(property.Description));
|
|
else
|
|
sb.AppendFormat("\t\t/// {0}\n", XmlCommentString(property.Name));
|
|
|
|
sb.Append("\t\t///</summary>\n");
|
|
}
|
|
|
|
WriteGeneratedCodeAttribute(sb, "\t\t");
|
|
sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias);
|
|
|
|
sb.Append("\t\tpublic virtual ");
|
|
WriteClrType(sb, property.ClrTypeName);
|
|
|
|
sb.AppendFormat(" {0} => ",
|
|
property.ClrName);
|
|
WriteNonGenericClrType(sb, GetModelsNamespace() + "." + mixinClrName);
|
|
sb.AppendFormat(".{0}(this, _publishedValueFallback);\n",
|
|
MixinStaticGetterName(property.ClrName));
|
|
}
|
|
|
|
private static string MixinStaticGetterName(string clrName)
|
|
{
|
|
return string.Format("Get{0}", clrName);
|
|
}
|
|
|
|
private void WriteProperty(StringBuilder sb, TypeModel type, PropertyModel property, string mixinClrName = null)
|
|
{
|
|
var mixinStatic = mixinClrName != null;
|
|
|
|
sb.Append("\n");
|
|
|
|
if (property.Errors != null)
|
|
{
|
|
sb.Append("\t\t/*\n");
|
|
sb.Append("\t\t * THIS PROPERTY CANNOT BE IMPLEMENTED, BECAUSE:\n");
|
|
sb.Append("\t\t *\n");
|
|
var first = true;
|
|
foreach (var error in property.Errors)
|
|
{
|
|
if (first) first = false;
|
|
else sb.Append("\t\t *\n");
|
|
foreach (var s in SplitError(error))
|
|
{
|
|
sb.Append("\t\t * ");
|
|
sb.Append(s);
|
|
sb.Append("\n");
|
|
}
|
|
}
|
|
sb.Append("\t\t *\n");
|
|
sb.Append("\n");
|
|
}
|
|
|
|
// Adds xml summary to each property containing
|
|
// property name and property description
|
|
if (!string.IsNullOrWhiteSpace(property.Name) || !string.IsNullOrWhiteSpace(property.Description))
|
|
{
|
|
sb.Append("\t\t///<summary>\n");
|
|
|
|
if (!string.IsNullOrWhiteSpace(property.Description))
|
|
sb.AppendFormat("\t\t/// {0}: {1}\n", XmlCommentString(property.Name), XmlCommentString(property.Description));
|
|
else
|
|
sb.AppendFormat("\t\t/// {0}\n", XmlCommentString(property.Name));
|
|
|
|
sb.Append("\t\t///</summary>\n");
|
|
}
|
|
|
|
WriteGeneratedCodeAttribute(sb, "\t\t");
|
|
if (!property.ModelClrType.IsValueType)
|
|
WriteMaybeNullAttribute(sb, "\t\t");
|
|
sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias);
|
|
|
|
if (mixinStatic)
|
|
{
|
|
sb.Append("\t\tpublic virtual ");
|
|
WriteClrType(sb, property.ClrTypeName);
|
|
sb.AppendFormat(" {0} => {1}(this, _publishedValueFallback);\n",
|
|
property.ClrName, MixinStaticGetterName(property.ClrName));
|
|
}
|
|
else
|
|
{
|
|
sb.Append("\t\tpublic virtual ");
|
|
WriteClrType(sb, property.ClrTypeName);
|
|
sb.AppendFormat(" {0} => this.Value",
|
|
property.ClrName);
|
|
if (property.ModelClrType != typeof(object))
|
|
{
|
|
sb.Append("<");
|
|
WriteClrType(sb, property.ClrTypeName);
|
|
sb.Append(">");
|
|
}
|
|
sb.AppendFormat("(_publishedValueFallback, \"{0}\");\n",
|
|
property.Alias);
|
|
}
|
|
|
|
if (property.Errors != null)
|
|
{
|
|
sb.Append("\n");
|
|
sb.Append("\t\t *\n");
|
|
sb.Append("\t\t */\n");
|
|
}
|
|
|
|
if (!mixinStatic) return;
|
|
|
|
var mixinStaticGetterName = MixinStaticGetterName(property.ClrName);
|
|
|
|
//if (type.StaticMixinMethods.Contains(mixinStaticGetterName)) return;
|
|
|
|
sb.Append("\n");
|
|
|
|
if (!string.IsNullOrWhiteSpace(property.Name))
|
|
sb.AppendFormat("\t\t/// <summary>Static getter for {0}</summary>\n", XmlCommentString(property.Name));
|
|
|
|
WriteGeneratedCodeAttribute(sb, "\t\t");
|
|
if (!property.ModelClrType.IsValueType)
|
|
WriteMaybeNullAttribute(sb, "\t\t", true);
|
|
sb.Append("\t\tpublic static ");
|
|
WriteClrType(sb, property.ClrTypeName);
|
|
sb.AppendFormat(" {0}(I{1} that, IPublishedValueFallback publishedValueFallback) => that.Value",
|
|
mixinStaticGetterName, mixinClrName);
|
|
if (property.ModelClrType != typeof(object))
|
|
{
|
|
sb.Append("<");
|
|
WriteClrType(sb, property.ClrTypeName);
|
|
sb.Append(">");
|
|
}
|
|
sb.AppendFormat("(publishedValueFallback, \"{0}\");\n",
|
|
property.Alias);
|
|
}
|
|
|
|
private static IEnumerable<string> SplitError(string error)
|
|
{
|
|
var p = 0;
|
|
while (p < error.Length)
|
|
{
|
|
var n = p + 50;
|
|
while (n < error.Length && error[n] != ' ') n++;
|
|
if (n >= error.Length) break;
|
|
yield return error.Substring(p, n - p);
|
|
p = n + 1;
|
|
}
|
|
if (p < error.Length)
|
|
yield return error.Substring(p);
|
|
}
|
|
|
|
private void WriteInterfaceProperty(StringBuilder sb, PropertyModel property)
|
|
{
|
|
if (property.Errors != null)
|
|
{
|
|
sb.Append("\t\t/*\n");
|
|
sb.Append("\t\t * THIS PROPERTY CANNOT BE IMPLEMENTED, BECAUSE:\n");
|
|
sb.Append("\t\t *\n");
|
|
var first = true;
|
|
foreach (var error in property.Errors)
|
|
{
|
|
if (first) first = false;
|
|
else sb.Append("\t\t *\n");
|
|
foreach (var s in SplitError(error))
|
|
{
|
|
sb.Append("\t\t * ");
|
|
sb.Append(s);
|
|
sb.Append("\n");
|
|
}
|
|
}
|
|
sb.Append("\t\t *\n");
|
|
sb.Append("\n");
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(property.Name))
|
|
sb.AppendFormat("\t\t/// <summary>{0}</summary>\n", XmlCommentString(property.Name));
|
|
WriteGeneratedCodeAttribute(sb, "\t\t");
|
|
if (!property.ModelClrType.IsValueType)
|
|
WriteMaybeNullAttribute(sb, "\t\t");
|
|
|
|
sb.Append("\t\t");
|
|
WriteClrType(sb, property.ClrTypeName);
|
|
sb.AppendFormat(" {0} {{ get; }}\n",
|
|
property.ClrName);
|
|
|
|
if (property.Errors != null)
|
|
{
|
|
sb.Append("\n");
|
|
sb.Append("\t\t *\n");
|
|
sb.Append("\t\t */\n");
|
|
}
|
|
}
|
|
|
|
// internal for unit tests
|
|
public void WriteClrType(StringBuilder sb, Type type)
|
|
{
|
|
var s = type.ToString();
|
|
|
|
if (type.IsGenericType)
|
|
{
|
|
var p = s.IndexOf('`');
|
|
WriteNonGenericClrType(sb, s.Substring(0, p));
|
|
sb.Append("<");
|
|
var args = type.GetGenericArguments();
|
|
for (var i = 0; i < args.Length; i++)
|
|
{
|
|
if (i > 0) sb.Append(", ");
|
|
WriteClrType(sb, args[i]);
|
|
}
|
|
sb.Append(">");
|
|
}
|
|
else
|
|
{
|
|
WriteNonGenericClrType(sb, s);
|
|
}
|
|
}
|
|
|
|
internal void WriteClrType(StringBuilder sb, string type)
|
|
{
|
|
var p = type.IndexOf('<');
|
|
if (type.Contains('<'))
|
|
{
|
|
WriteNonGenericClrType(sb, type.Substring(0, p));
|
|
sb.Append("<");
|
|
var args = type.Substring(p + 1).TrimEnd(Constants.CharArrays.GreaterThan).Split(Constants.CharArrays.Comma); // fixme will NOT work with nested generic types
|
|
for (var i = 0; i < args.Length; i++)
|
|
{
|
|
if (i > 0) sb.Append(", ");
|
|
WriteClrType(sb, args[i]);
|
|
}
|
|
sb.Append(">");
|
|
}
|
|
else
|
|
{
|
|
WriteNonGenericClrType(sb, type);
|
|
}
|
|
}
|
|
|
|
private void WriteNonGenericClrType(StringBuilder sb, string s)
|
|
{
|
|
// map model types
|
|
s = Regex.Replace(s, @"\{(.*)\}\[\*\]", m => ModelsMap[m.Groups[1].Value + "[]"]);
|
|
|
|
// takes care eg of "System.Int32" vs. "int"
|
|
if (TypesMap.TryGetValue(s, out string typeName))
|
|
{
|
|
sb.Append(typeName);
|
|
return;
|
|
}
|
|
|
|
// if full type name matches a using clause, strip
|
|
// so if we want Umbraco.Core.Models.IPublishedContent
|
|
// and using Umbraco.Core.Models, then we just need IPublishedContent
|
|
typeName = s;
|
|
string typeUsing = null;
|
|
var p = typeName.LastIndexOf('.');
|
|
if (p > 0)
|
|
{
|
|
var x = typeName.Substring(0, p);
|
|
if (Using.Contains(x))
|
|
{
|
|
typeName = typeName.Substring(p + 1);
|
|
typeUsing = x;
|
|
}
|
|
else if (x == ModelsNamespace) // that one is used by default
|
|
{
|
|
typeName = typeName.Substring(p + 1);
|
|
typeUsing = ModelsNamespace;
|
|
}
|
|
}
|
|
|
|
// nested types *after* using
|
|
typeName = typeName.Replace("+", ".");
|
|
|
|
// symbol to test is the first part of the name
|
|
// so if type name is Foo.Bar.Nil we want to ensure that Foo is not ambiguous
|
|
p = typeName.IndexOf('.');
|
|
var symbol = p > 0 ? typeName.Substring(0, p) : typeName;
|
|
|
|
// what we should find - WITHOUT any generic <T> thing - just the type
|
|
// no 'using' = the exact symbol
|
|
// a 'using' = using.symbol
|
|
var match = typeUsing == null ? symbol : (typeUsing + "." + symbol);
|
|
|
|
// if not ambiguous, be happy
|
|
if (!IsAmbiguousSymbol(symbol, match))
|
|
{
|
|
sb.Append(typeName);
|
|
return;
|
|
}
|
|
|
|
// symbol is ambiguous
|
|
// if no 'using', must prepend global::
|
|
if (typeUsing == null)
|
|
{
|
|
sb.Append("global::");
|
|
sb.Append(s.Replace("+", "."));
|
|
return;
|
|
}
|
|
|
|
// could fullname be non-ambiguous?
|
|
// note: all-or-nothing, not trying to segment the using clause
|
|
typeName = s.Replace("+", ".");
|
|
p = typeName.IndexOf('.');
|
|
symbol = typeName.Substring(0, p);
|
|
match = symbol;
|
|
|
|
// still ambiguous, must prepend global::
|
|
if (IsAmbiguousSymbol(symbol, match))
|
|
sb.Append("global::");
|
|
|
|
sb.Append(typeName);
|
|
}
|
|
|
|
private static string XmlCommentString(string s)
|
|
{
|
|
return s.Replace('<', '{').Replace('>', '}').Replace('\r', ' ').Replace('\n', ' ');
|
|
}
|
|
|
|
private static readonly IDictionary<string, string> TypesMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
{ "System.Int16", "short" },
|
|
{ "System.Int32", "int" },
|
|
{ "System.Int64", "long" },
|
|
{ "System.String", "string" },
|
|
{ "System.Object", "object" },
|
|
{ "System.Boolean", "bool" },
|
|
{ "System.Void", "void" },
|
|
{ "System.Char", "char" },
|
|
{ "System.Byte", "byte" },
|
|
{ "System.UInt16", "ushort" },
|
|
{ "System.UInt32", "uint" },
|
|
{ "System.UInt64", "ulong" },
|
|
{ "System.SByte", "sbyte" },
|
|
{ "System.Single", "float" },
|
|
{ "System.Double", "double" },
|
|
{ "System.Decimal", "decimal" }
|
|
};
|
|
}
|
|
}
|