I need some way to get the Name of a Type, when type.IsGenericType = true.
Type t = typeof(List<String>);
MessageBox.Show( ..?.. );
What I want, is a message box to pop up with List showing... how can I do that?
You can implement an extension method to get the "friendly name" of a type, like this:
public static class TypeNameExtensions
{
public static string GetFriendlyName(this Type type)
{
string friendlyName = type.Name;
if (type.IsGenericType)
{
int iBacktick = friendlyName.IndexOf('`');
if (iBacktick > 0)
{
friendlyName = friendlyName.Remove(iBacktick);
}
friendlyName += "<";
Type[] typeParameters = type.GetGenericArguments();
for (int i = 0; i < typeParameters.Length; ++i)
{
string typeParamName = GetFriendlyName(typeParameters[i]);
friendlyName += (i == 0 ? typeParamName : "," + typeParamName);
}
friendlyName += ">";
}
return friendlyName;
}
}
With this in your project, you can now say:
MessageBox.Show(t.GetFriendlyName());
And it will display "List<String>".
I know the OP didn't ask for the generic type parameters, but I prefer it that way. ;-)
Namespaces, standard aliases for built-in types, and use of StringBuilder left as an exercise for the reader. ;-)
Type t = ...;
if (t.IsGenericType)
{
Type g = t.GetGenericTypeDefinition();
MessageBox.Show(g.Name); // displays "List`1"
MessageBox.Show(g.Name.Remove(g.Name.IndexOf('`'))); // displays "List"
}
My take on yoyo's approach. Ensures more friendly names for primitives, handles arrays and is recursive to handle nested generics. Also unit tests.
private static readonly Dictionary<Type, string> _typeToFriendlyName = new Dictionary<Type, string>
{
{ typeof(string), "string" },
{ typeof(object), "object" },
{ typeof(bool), "bool" },
{ typeof(byte), "byte" },
{ typeof(char), "char" },
{ typeof(decimal), "decimal" },
{ typeof(double), "double" },
{ typeof(short), "short" },
{ typeof(int), "int" },
{ typeof(long), "long" },
{ typeof(sbyte), "sbyte" },
{ typeof(float), "float" },
{ typeof(ushort), "ushort" },
{ typeof(uint), "uint" },
{ typeof(ulong), "ulong" },
{ typeof(void), "void" }
};
public static string GetFriendlyName(this Type type)
{
string friendlyName;
if (_typeToFriendlyName.TryGetValue(type, out friendlyName))
{
return friendlyName;
}
friendlyName = type.Name;
if (type.IsGenericType)
{
int backtick = friendlyName.IndexOf('`');
if (backtick > 0)
{
friendlyName = friendlyName.Remove(backtick);
}
friendlyName += "<";
Type[] typeParameters = type.GetGenericArguments();
for (int i = 0; i < typeParameters.Length; i++)
{
string typeParamName = typeParameters[i].GetFriendlyName();
friendlyName += (i == 0 ? typeParamName : ", " + typeParamName);
}
friendlyName += ">";
}
if (type.IsArray)
{
return type.GetElementType().GetFriendlyName() + "[]";
}
return friendlyName;
}
[TestFixture]
public class TypeHelperTest
{
[Test]
public void TestGetFriendlyName()
{
Assert.AreEqual("string", typeof(string).FriendlyName());
Assert.AreEqual("int[]", typeof(int[]).FriendlyName());
Assert.AreEqual("int[][]", typeof(int[][]).FriendlyName());
Assert.AreEqual("KeyValuePair<int, string>", typeof(KeyValuePair<int, string>).FriendlyName());
Assert.AreEqual("Tuple<int, string>", typeof(Tuple<int, string>).FriendlyName());
Assert.AreEqual("Tuple<KeyValuePair<object, long>, string>", typeof(Tuple<KeyValuePair<object, long>, string>).FriendlyName());
Assert.AreEqual("List<Tuple<int, string>>", typeof(List<Tuple<int, string>>).FriendlyName());
Assert.AreEqual("Tuple<short[], string>", typeof(Tuple<short[], string>).FriendlyName());
}
}
Assuming you just want to see that its List<T> instead of List<string> you'd need to do:
MessageBox.Show(t.GetGenericTypeDefinition().FullName)
See http://msdn.microsoft.com/en-us/library/system.type.getgenerictypedefinition.aspx
public static class TypeNameExtensions
{
public static string GetFriendlyName(this Type type)
{
var friendlyName = type.Name;
if (!type.IsGenericType) return friendlyName;
var iBacktick = friendlyName.IndexOf('`');
if (iBacktick > 0) friendlyName = friendlyName.Remove(iBacktick);
var genericParameters = type.GetGenericArguments().Select(x => x.GetFriendlyName());
friendlyName += "<" + string.Join(", ", genericParameters) + ">";
return friendlyName;
}
}
Here is my take on this. I did not put the backtick check since for what I see, it's always there. You can add it if you want but I like to keep things simple.
public static string GetFriendlyName(this Type type)
{
if (type.IsGenericType)
{
var name = type.Name.Substring(0, type.Name.IndexOf('`'));
var types = string.Join(",", type.GetGenericArguments().Select(GetFriendlyName));
return $"{name}<{types}>";
}
else
{
return type.Name;
}
}
I know this is an old question, but a colleague and myself needed to do this for some intellisense/roslyn work. The optimal solution appeared to be Ali's solution, but it doesn't work for nested types:
int i = 1; //would work
List<string> listTest = new List<string>(); //would work
Dictionary<string, int> dictTest = new Dictionary<string, int>(); //would work
Dictionary<int, List<string>> nestTest = new Dictionary<int, List<string>>(); //would fail
Dictionary<int, List<Dictionary<string, List<object>>>> superNestTest = new Dictionary<int, List<Dictionary<string, List<object>>>>(); //would fail
Dictionary<int, List<Dictionary<string, int>>> superNestTest2 = new Dictionary<int, List<Dictionary<string, int>>>(); //would fail
In order to solve these issues, I converted the function into a recursive method:
public static class TypeExtensions
{
public static string GetFriendlyName(this Type type)
{
string friendlyName = type.FullName;
if (type.IsGenericType)
{
friendlyName = GetTypeString(type);
}
return friendlyName;
}
private static string GetTypeString(Type type)
{
var t = type.AssemblyQualifiedName;
var output = new StringBuilder();
List<string> typeStrings = new List<string>();
int iAssyBackTick = t.IndexOf('`') + 1;
output.Append(t.Substring(0, iAssyBackTick - 1).Replace("[", string.Empty));
var genericTypes = type.GetGenericArguments();
foreach (var genType in genericTypes)
{
typeStrings.Add(genType.IsGenericType ? GetTypeString(genType) : genType.ToString());
}
output.Append($"<{string.Join(",", typeStrings)}>");
return output.ToString();
}
}
running for the previous examples/test cases yielded the following outputs:
System.Int32
System.Collections.Generic.List<System.String>
System.Collections.Generic.Dictionary<System.String,System.Int32>
System.Collections.Generic.Dictionary<System.Int32,System.Collections.Generic.List<System.String>>
System.Collections.Generic.Dictionary<System.Int32,System.Collections.Generic.List<System.Collections.Generic.Dictionary<System.String,System.Collections.Generic.List<System.Object>>>>
System.Collections.Generic.Dictionary<System.Int32,System.Collections.Generic.List<System.Collections.Generic.Dictionary<System.String,System.Int32>>>
I spent some time trying to resolve the nested types issue so wanted to document this here to ensure anyone else in future can save some considerable time (and headaches!). I have checked the performance as well, and it is in the microseconds to complete (8 microseconds in the case of the last scenario:
Performance results
(Variables names used from original scenario list)
"i" | 43uS
"listTest" | 3uS
"dictTest" | 2uS
"nestTest" | 5uS
"superNestTest" | 9uS
"superNestTest2" | 9uS
Average times after performing the above code 200 times on each scenario
Here is a complete implementation based on the previous answers supporting both Aliases (including Nullable) and Arrays:
public static class TypeNameExtensions
{
public static string GetFriendlyName(this Type type, bool aliasNullable = true, bool includeSpaceAfterComma = true)
{
TryGetInnerElementType(ref type, out string arrayBrackets);
if (!TryGetNameAliasNonArray(type, out string friendlyName))
{
if (!type.IsGenericType)
{
friendlyName = type.Name;
}
else
{
if (aliasNullable && type.GetGenericTypeDefinition() == typeof(System.Nullable<>))
{
string generics = GetFriendlyName(type.GetGenericArguments()[0]);
friendlyName = generics + "?";
}
else
{
string generics = GetFriendlyGenericArguments(type, includeSpaceAfterComma);
int iBacktick = type.Name.IndexOf('`');
friendlyName = (iBacktick > 0 ? type.Name.Remove(iBacktick) : type.Name)
+ $"<{generics}>";
}
}
}
return friendlyName + arrayBrackets;
}
public static bool TryGetNameAlias(this Type type, out string alias)
{
TryGetInnerElementType(ref type, out string arrayBrackets);
if (!TryGetNameAliasNonArray(type, out alias))
return false;
alias += arrayBrackets;
return true;
}
private static string GetFriendlyGenericArguments(Type type, bool includeSpaceAfterComma)
=> string.Join(
includeSpaceAfterComma ? ", " : ",",
type.GetGenericArguments().Select(t => t.GetFriendlyName())
);
private static bool TryGetNameAliasNonArray(Type type, out string alias)
=> (alias = TypeAliases[(int)Type.GetTypeCode(type)]) != null
&& !type.IsEnum;
private static bool TryGetInnerElementType(ref Type type, out string arrayBrackets)
{
arrayBrackets = null;
if (!type.IsArray)
return false;
do
{
arrayBrackets += "[" + new string(',', type.GetArrayRank() - 1) + "]";
type = type.GetElementType();
}
while (type.IsArray);
return true;
}
private static readonly string[] TypeAliases = {
"void", // 0
null, // 1 (any other type)
"DBNull", // 2
"bool", // 3
"char", // 4
"sbyte", // 5
"byte", // 6
"short", // 7
"ushort", // 8
"int", // 9
"uint", // 10
"long", // 11
"ulong", // 12
"float", // 13
"double", // 14
"decimal", // 15
null, // 16 (DateTime)
null, // 17 (-undefined-)
"string", // 18
};
}
Tested with nonsense such as:
var type = typeof(Dictionary<string[,], List<int?[,][]>[,,]>[]);
var name = type.GetFriendlyName();
Console.WriteLine(name);
And it does indeed return: "Dictionary<string[,], List<int?[,][]>[,,]>[]"
Edit: Updated to properly handle enum types.
i have improved yoyos version for the usage in Code Generation.
Note that all types are now referenced full qualified => global::System.String.
public static string GetFriendlyTypeName(Type type)
{
string friendlyName = type.Name;
if (type.IsGenericType)
{
int iBacktick = friendlyName.IndexOf('`');
if (iBacktick > 0)
{
friendlyName = friendlyName.Remove(iBacktick);
}
friendlyName += "<";
Type[] typeParameters = type.GetGenericArguments();
for (int i = 0; i < typeParameters.Length; ++i)
{
string typeParamName = GetFriendlyTypeName(typeParameters[i]);
friendlyName += (i == 0 ? typeParamName : "," + typeParamName);
}
friendlyName += ">";
friendlyName = "global::" + type.Namespace + "." + friendlyName;
}
else
{
friendlyName = "global::" + type.FullName;
}
return friendlyName.Replace('+', '.');
}
In the latest version of C#, you can use:
var s = x.GetType().ShortDisplayName();
And it returns Thing<IFoo>
Edit: Sorry, that only works as an extension on EF Core. :(
Related
I want to use is operator in c# condition statement. my code is like this:
public static string returnProperColumnValue(string columnName, object columnValue)
{
if (columnValue is int || columnValue is float || columnValue is long || ..... ))
{
return columnValue.ToString();
}
else if (columnValue is string)
{
return "'" + columnValue + "'";
}
else if (columnValue.GetType().IsGenericType && columnValue is IDictionary)
{
var dictionaryType = columnValue.GetType().GetGenericArguments().First().Name;
//to do
return "";
}
else if (columnValue.GetType().IsGenericType && (columnValue is IEnumerable))
{
var listType = columnValue.GetType().GetGenericArguments().Single().Name;
if (listType is int || ....... )
return columnName + "+" + "[" + columnValue.ToString() + "]";
else if (listType is string)
return columnName + "+" + "{'" + columnValue.ToString() + "'} ";
}
return "";
}
I want to check lots of variable types in the if clause.
Therefore, I want to use is operator in shorter way like this:
public void properColumnValue(object columnValue)
{
if (columnValue is ( int || decimal || ... ) )
{
//do stuff for int
}
//other conditions
}
Is it possible to factorize the is operation?
No, the is operator cannot be factored like this. Maybe you could use the IsInstanceOfType method, which is a member of System.Type:
public void properColumnValue(object columnValue)
{
Type[] types = new[] {typeof (int), typeof (decimal), typeof (long)};
bool b = types.Any(x => x.IsInstanceOfType(columnValue));
if (b)
{
// do stuff
}
}
Apart from Kapol's idea you can also use the Contains method to see if the actual type of the value contained in your columnValue is a member of the array:
public void properColumnValue(object columnValue)
{
Type[] number_types = new[] {typeof (int), typeof (decimal), typeof (long)};
if (number_types.Contains(columnValue?.GetType()))
{
// convert to long, or whatever you need it to be:
long value = Convert.ToInt64(columnValue);
//... do something with the value
}
}
Or you can make that a static readonly array at the class level to speed things up a tiny bit:
static readonly Type[] number_types = new[] {typeof (int), typeof (decimal), typeof (long)};
public void properColumnValue(object columnValue)
{
if (number_types.Contains(columnValue?.GetType()))
{
// convert to long, or whatever you need it to be:
long value = Convert.ToInt64(columnValue);
//... do something with the value
}
}
At least that way you're only creating the array once.
Every logical operation requires both left and right side arguments, and is operator is not a method you cannot use it like columnValue is ( int || decimal || ...)
What you can do is create custom extension method for this, and pass types (or string names of types) as parameters :
public static class Helper
{
static Dictionary<string, Type> _types = new Dictionary<string, Type>()
{
{"int", typeof(int)}, {"string", typeof(string)},
{"double", typeof(double)}, {"decimal", typeof(decimal)},
// ... etc
};
public static bool Is<T>(this T instance, params string[] types)
{
Type iType = instance.GetType();
for (int i = 0; i < types.Length; i++)
if (_types.ContainsKey(types[i]) && _types[types[i]] == iType)
return true;
return false;
}
}
And use it :
public void properColumnValue(object columnValue)
{
if (columnValue.Is("int", "decimal", "double") ... ))
{
//do stuff for int, decimal or double
}
}
I have a GetDynamicParameters() on cmdlet Get-DateSlain that does something like this:
public object GetDynamicParameters()
{
List<string> houseList = {"Stark", "Lannister", "Tully"};
var attributes = new Collection<Attribute>
{
new ParameterAttribute
{
HelpMessage = "Enter a house name",
},
new ValidateSetAttribute(houseList.ToArray()),
};
if (!this.ContainsKey("House"))
{
this.runtimeParameters.Add("House", new RuntimeDefinedParameter("House", typeof(string), attributes));
}
}
And this works as expected - users can type Get-DateSlain -House, and tab through the available houses. However, once a house is chosen, I want to be able to narrow down the results to characters in that house. Furthermore, if it's house 'Stark', I want to allow a -Wolf parameter. So to implement (some value validity checks removed for brevity):
public object GetDynamicParameters()
{
if (this.runtimeParameters.ContainsKey("House"))
{
// We already have this key - no need to re-add. However, now we can add other parameters
var house = this.runtimeParameters["House"].Value.ToString();
if (house == "Stark")
{
List<string> characters = { "Ned", "Arya", "Rob" };
var attributes = new Collection<Attribute>
{
new ParameterAttribute
{
HelpMessage = "Enter a character name",
},
new ValidateSetAttribute(characters.ToArray()),
};
this.runtimeParameters.Add("Character", new RuntimeDefinedParameter("Character", typeof(string), attributes));
List<string> wolves = { "Shaggydog", "Snow", "Lady" };
var attributes = new Collection<Attribute>
{
new ParameterAttribute
{
HelpMessage = "Enter a wolf name",
},
new ValidateSetAttribute(wolves.ToArray()),
};
this.runtimeParameters.Add("Wolf", new RuntimeDefinedParameter("Wolf", typeof(string), attributes));
}
else if (house == "Lannister")
{
List<string> characters = { "Jaimie", "Cersei", "Tywin" };
// ...
}
// ...
return this.runtimeParameters;
}
List<string> houseList = {"Stark", "Lannister", "Tully"};
var attributes = new Collection<Attribute>
{
new ParameterAttribute
{
HelpMessage = "Enter a house name",
},
new ValidateSetAttribute(houseList.ToArray()),
};
this.runtimeParameters.Add("House", new RuntimeDefinedParameter("House", typeof(string), attributes));
}
This looks like it should work, but it doesn't. The GetDynamicParameters function is only called once, and that is before a value is supplied to this.runtimeParameters["House"]. Since it doesn't re-evaluate after that value is filled in, the additional field(s) are never added, and any logic in ProcessRecord that relies on these fields will fail.
So - is there a way to have multiple dynamic parameters that rely on each other?
Have a look a the aswer to this question, it shows a way to access the values of other dynamic parameters in the GetDynamicParameters method:
Powershell module: Dynamic mandatory hierarchical parameters
I adapted the code from the mentioned answer so it can handle SwitchParameters and the raw input parameter is converted to the actual type of the cmdlet parameter. It does not work if the dynamic parameter you want to get the value for is passed via pipeline. I think that is not possible because dynamic parameters are always created before pipeline input is evaluated. Here it is:
public static class DynamicParameterExtension
{
public static T GetUnboundValue<T>(this PSCmdlet cmdlet, string paramName, int unnamedPosition = -1))
{
var context = TryGetProperty(cmdlet, "Context");
var processor = TryGetProperty(context, "CurrentCommandProcessor");
var parameterBinder = TryGetProperty(processor, "CmdletParameterBinderController");
var args = TryGetProperty(parameterBinder, "UnboundArguments") as System.Collections.IEnumerable;
if (args != null)
{
var isSwitch = typeof(SwitchParameter) == typeof(T);
var currentParameterName = string.Empty;
object unnamedValue = null;
var i = 0;
foreach (var arg in args)
{
var isParameterName = TryGetProperty(arg, "ParameterNameSpecified");
if (isParameterName != null && true.Equals(isParameterName))
{
var parameterName = TryGetProperty(arg, "ParameterName") as string;
currentParameterName = parameterName;
if (isSwitch && string.Equals(currentParameterName, paramName, StringComparison.OrdinalIgnoreCase))
{
return (T)(object)new SwitchParameter(true);
}
continue;
}
var parameterValue = TryGetProperty(arg, "ArgumentValue");
if (currentParameterName != string.Empty)
{
if (string.Equals(currentParameterName, paramName, StringComparison.OrdinalIgnoreCase))
{
return ConvertParameter<T>(parameterValue);
}
}
else if (i++ == unnamedPosition)
{
unnamedValue = parameterValue;
}
currentParameterName = string.Empty;
}
if (unnamedValue != null)
{
return ConvertParameter<T>(unnamedValue);
}
}
return default(T);
}
static T ConvertParameter<T>(this object value)
{
if (value == null || Equals(value, default(T)))
{
return default(T);
}
var psObject = value as PSObject;
if (psObject != null)
{
return psObject.BaseObject.ConvertParameter<T>();
}
if (value is T)
{
return (T)value;
}
var constructorInfo = typeof(T).GetConstructor(new[] { value.GetType() });
if (constructorInfo != null)
{
return (T)constructorInfo.Invoke(new[] { value });
}
try
{
return (T)Convert.ChangeType(value, typeof(T));
}
catch (Exception)
{
return default(T);
}
}
static object TryGetProperty(object instance, string fieldName)
{
if (instance == null || string.IsNullOrEmpty(fieldName))
{
return null;
}
const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;
var propertyInfo = instance.GetType().GetProperty(fieldName, bindingFlags);
try
{
if (propertyInfo != null)
{
return propertyInfo.GetValue(instance, null);
}
var fieldInfo = instance.GetType().GetField(fieldName, bindingFlags);
return fieldInfo?.GetValue(instance);
}
catch (Exception)
{
return null;
}
}
}
So for your example you should be able to use it like:
public object GetDynamicParameters()
{
var houseList = new List<string> { "Stark", "Lannister", "Tully" };
var attributes = new Collection<Attribute>
{
new ParameterAttribute { HelpMessage = "Enter a house name" },
new ValidateSetAttribute(houseList.ToArray()),
};
var runtimeParameters = new RuntimeDefinedParameterDictionary
{
{"House", new RuntimeDefinedParameter("House", typeof (string), attributes)}
};
var selectedHouse = this.GetUnboundValue<string>("House");
//... add parameters dependant on value of selectedHouse
return runtimeParameters;
}
After all I'm not sure if it's a good idea trying to get those dynamic parameter values in the first place. It is obviously not supported by PowerShell Cmdlet API (see all the reflection to access private members in the GetUnboundValue method), you have to reimplement the PowerShell parameter conversion magic (see ConvertParameter, I'm sure I missed some cases there) and there is the restriction with pipelined values. Usage at your own risk:)
When de-serializing a flagged enum that is decorated with a EnumMemberAttribute with a value containing a space a SerializationException is thrown. The space in the value is treated as a separator.
Is there a way to change the separator or put the values in quotes ? Or is there even a more simple solution ?
Options I already am considering are :
Replacing the flagged enum with a list of this enum type
Replacing the spaces with underscores
This is used in a WCF service, and I am
aware that enums in datacontracts by some are considered a bad thing.
So I am also thinking about losing the enum’s all together.
But I really feel that this should be something configurable or something other people already solved. But I can't find anything.
I have boiled the problem down to a simple unit test. The code below results in:
Message=Invalid enum value 'Test' cannot be deserialized into type 'UnitTests.TestEnum'. Ensure that the necessary enum values are present and are marked with EnumMemberAttribute attribute if the type has DataContractAttribute attribute.
Source=System.Runtime.Serialization
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Xml;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests
{
[TestClass]
public class EnumSerizalizationTests
{
[TestMethod]
public void SerializingAndDesrializingAFlaggedEnumShouldResultInSameEnumValues()
{
//Arrange
var orgObject = new TestClass { Value = TestEnum.TestValue1 | TestEnum.TestValue2 };
//Act
var temp = DataContractSerializeObject(orgObject);
var newObject = DataContractDeSerializeObject<TestClass>(temp);
//Assert
newObject.ShouldBeEquivalentTo(orgObject, "Roundtripping serialization should result in same value");
}
public string DataContractSerializeObject<T>(T objectToSerialize)
{
using (var output = new StringWriter())
{
using (var writer = new XmlTextWriter(output) {Formatting = Formatting.Indented})
{
new DataContractSerializer(typeof (T)).WriteObject(writer, objectToSerialize);
return output.GetStringBuilder().ToString();
}
}
}
public T DataContractDeSerializeObject<T>(string stringToDeSerialize)
{
DataContractSerializer ser = new DataContractSerializer(typeof(T));
T result;
using (StringReader stringReader = new StringReader(stringToDeSerialize))
{
using (XmlReader xmlReader = XmlReader.Create(stringReader))
{
result = (T)ser.ReadObject(xmlReader);
}
}
return result;
}
}
[DataContract]
[KnownType(typeof(TestEnum))]
public class TestClass
{
[DataMember]
public TestEnum Value { get; set; }
}
[Flags]
[DataContract]
public enum TestEnum
{
[EnumMember(Value = "Test value one")]
TestValue1 = 1,
[EnumMember(Value = "Test value two")]
TestValue2 = 2,
[EnumMember]
TestValue3 = 4,
[EnumMember]
TestValue4 = 8,
}
}
You can't use space in values because DataContractSerializer uses it and it is hardcoded. See the source and the post. But if you really want to use space between words, then use one of the listed solutions:
The first way. Use other whitespace characters such as three-per-em space in values. But you will have another problem - there is no visual separator between values.
<TestClass xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication">
<Value>Test value one Test value two</Value>
</TestClass>
The second way is to use IDataContractSurrogate. This way will produce the XML listed below:
<TestClass xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication">
<Value i:type="EnumValueOfTestEnum9cBcd6LT">Test value one, Test value two</Value>
</TestClass>
How does it work? We will just wrap our enumeration in the process of serialization and unwrap in case of deserialization. In order to do that we should use IDataContractSurrogate:
new DataContractSerializerSettings()
{
DataContractSurrogate = new EnumSurrogate(),
KnownTypes = new Type[] { typeof(EnumValue<TestEnum>) }
};
public class EnumSurrogate : IDataContractSurrogate
{
#region IDataContractSurrogate Members
public object GetCustomDataToExport(Type clrType, Type dataContractType)
{
return null;
}
public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
{
return null;
}
public Type GetDataContractType(Type type)
{
return type;
}
public object GetDeserializedObject(object obj, Type targetType)
{
IEnumValue enumValue = obj as IEnumValue;
if (enumValue!= null)
{ return enumValue.Value; }
return obj;
}
public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
{
}
public object GetObjectToSerialize(object obj, Type targetType)
{
if (obj != null)
{
Type type = obj.GetType();
if (type.IsEnum && Attribute.IsDefined(type, typeof(FlagsAttribute)))
{ return Activator.CreateInstance(typeof(EnumValue<>).MakeGenericType(type), obj); }
}
return obj;
}
public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
{
return null;
}
public CodeTypeDeclaration ProcessImportedType(CodeTypeDeclaration typeDeclaration, CodeCompileUnit compileUnit)
{
return null;
}
#endregion
}
public interface IEnumValue : IXmlSerializable
{
object Value { get; }
}
[Serializable]
public class EnumValue<T> : IEnumValue
where T : struct
{
#region Fields
private Enum value;
private static Type enumType;
private static long[] values;
private static string[] names;
private static bool isULong;
#endregion
#region Constructors
static EnumValue()
{
enumType = typeof(T);
if (!enumType.IsEnum)
{ throw new InvalidOperationException(); }
FieldInfo[] fieldInfos = enumType.GetFields(BindingFlags.Static | BindingFlags.Public);
values = new long[fieldInfos.Length];
names = new string[fieldInfos.Length];
isULong = Enum.GetUnderlyingType(enumType) == typeof(ulong);
for (int i = 0; i < fieldInfos.Length; i++)
{
FieldInfo fieldInfo = fieldInfos[i];
EnumMemberAttribute enumMemberAttribute = (EnumMemberAttribute)fieldInfo
.GetCustomAttributes(typeof(EnumMemberAttribute), false)
.FirstOrDefault();
IConvertible value = (IConvertible)fieldInfo.GetValue(null);
values[i] = (isULong)
? (long)value.ToUInt64(null)
: value.ToInt64(null);
names[i] = (enumMemberAttribute == null || string.IsNullOrEmpty(enumMemberAttribute.Value))
? fieldInfo.Name
: enumMemberAttribute.Value;
}
}
public EnumValue()
{
}
public EnumValue(Enum value)
{
this.value = value;
}
#endregion
#region IXmlSerializable Members
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
string stringValue = reader.ReadElementContentAsString();
long longValue = 0;
int i = 0;
// Skip initial spaces
for (; i < stringValue.Length && stringValue[i] == ' '; i++) ;
// Read comma-delimited values
int startIndex = i;
int nonSpaceIndex = i;
int count = 0;
for (; i < stringValue.Length; i++)
{
if (stringValue[i] == ',')
{
count = nonSpaceIndex - startIndex + 1;
if (count > 1)
{ longValue |= ReadEnumValue(stringValue, startIndex, count); }
nonSpaceIndex = ++i;
// Skip spaces
for (; i < stringValue.Length && stringValue[i] == ' '; i++) ;
startIndex = i;
if (i == stringValue.Length)
{ break; }
}
else
{
if (stringValue[i] != ' ')
{ nonSpaceIndex = i; }
}
}
count = nonSpaceIndex - startIndex + 1;
if (count > 1)
longValue |= ReadEnumValue(stringValue, startIndex, count);
value = (isULong)
? (Enum)Enum.ToObject(enumType, (ulong)longValue)
: (Enum)Enum.ToObject(enumType, longValue);
}
public void WriteXml(XmlWriter writer)
{
long longValue = (isULong)
? (long)((IConvertible)value).ToUInt64(null)
: ((IConvertible)value).ToInt64(null);
int zeroIndex = -1;
bool noneWritten = true;
for (int i = 0; i < values.Length; i++)
{
long current = values[i];
if (current == 0)
{
zeroIndex = i;
continue;
}
if (longValue == 0)
{ break; }
if ((current & longValue) == current)
{
if (noneWritten)
{ noneWritten = false; }
else
{ writer.WriteString(","); }
writer.WriteString(names[i]);
longValue &= ~current;
}
}
if (longValue != 0)
{ throw new InvalidOperationException(); }
if (noneWritten && zeroIndex >= 0)
{ writer.WriteString(names[zeroIndex]); }
}
#endregion
#region IEnumValue Members
public object Value
{
get { return value; }
}
#endregion
#region Private Methods
private static long ReadEnumValue(string value, int index, int count)
{
for (int i = 0; i < names.Length; i++)
{
string name = names[i];
if (count == name.Length && string.CompareOrdinal(value, index, name, 0, count) == 0)
{ return values[i]; }
}
throw new InvalidOperationException();
}
#endregion
}
The third way is to emit dynamically the class, if base class has flagged Enum properties, replace them with string properties and use instances of the generated class as surrogates.
I'm looking to take an in-memory object (or JSON serialization of an object) and emit C# code to produce an equivalent object.
This would be useful for pulling known-good examples from a repository to use as starting points in unit tests. We have considered deserializing JSON, but C# code would have an edge when it comes to refactoring.
There is an interesting Visual Studio extension that addresses this; the Object Exporter. It allows serialization of an in-memory object into C# Object initialization code, JSON and XML. I haven't tried it yet but looks intriguing; will update after trying it out.
If your model is simple, you could use reflection and a string builder to output C# directly. I've done this to populate unit test data exactly as you discussed.
The code sample below was written in a few minutes and generated an object initializer that needed some hand tweaking. A more robust / less buggy function could be written if you plan on doing this a lot.
The second function is recursive, iterating over any Lists within the object, and generating code for those as well.
Disclaimer: This worked for my simple model with basic data types. It generated code that needed cleanup but allowed me to move on quickly. It is only here to serve as an example of how this could be done. Hopefully, it inspires someone to write their own.
In my case, I had an instance of this large dataset (results) that was loaded from the database. In order to remove the database dependency from my unit test, I handed the object to this function which spits out the code that allowed me to mock the object in my test class.
private void WriteInstanciationCodeFromObject(IList results)
{
//declare the object that will eventually house C# initialization code for this class
var testMockObject = new System.Text.StringBuilder();
//start building code for this object
ConstructAndFillProperties(testMockObject, results);
var codeOutput = testMockObject.ToString();
}
private void ConstructAndFillProperties(StringBuilder testMockObject, IList results)
{
testMockObject.AppendLine("var testMock = new " + results.GetType().ToString() + "();");
foreach (object obj in results)
{
//if this object is a list, write code for its contents
if (obj.GetType().GetInterfaces().Contains(typeof(IList)))
{
ConstructAndFillProperties(testMockObject, (IList)obj);
}
testMockObject.AppendLine("testMock.Add(new " + obj.GetType().Name + "() {");
foreach (var property in obj.GetType().GetProperties())
{
//if this property is a list, write code for its contents
if (property.PropertyType.GetInterfaces().Contains(typeof(IList)))
{
ConstructAndFillProperties(testMockObject, (IList)property.GetValue(obj, null));
}
testMockObject.AppendLine(property.Name + " = (" + property.PropertyType + ")\"" + property.GetValue(obj, null) + "\",");
}
testMockObject.AppendLine("});");
}
}
It's possible the object will have a TypeConverter that supports conversion to InstanceDescriptor, which is what the WinForms designer uses when emitting C# code to generate an object. If it can't convert to an InstanceDescriptor, it will attempt to use a parameterless constructor and simply set public properties. The InstanceDescriptor mechanism is handy, since it allows you to specify various construction options such as constructors with parameters or even static factory method calls.
I have some utility code I've written that emits loading of an in-memory object using IL, which basically follows the above pattern (use InstanceDescriptor if possible and, if not, simply write public properties.) Note that this will only produce an equivalent object if the InstanceDescriptor is properly implemented or setting public properties is sufficient to restore object state. If you're emitting IL, you can also cheat and read/write field values directly (this is what the DataContractSerializer supports), but there are a lot of nasty corner cases to consider.
I'm a novice at this as well, but I also needed to take a C# object that defined a hierarchy and extract it to an object initializer to ease setting up a unit test. I borrowed heavily from the above and wound up with this. I'd like to improve the way it handles recognizing user classes.
http://github.com/jefflomax/csharp-object-to-object-literal/blob/master/Program.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ObjectInitializer
{
public class Program
{
public enum Color { Red, Green, Blue, Yellow, Fidget } ;
public class Foo
{
public int FooId { get; set; }
public string FooName { get; set; }
}
public class Thing
{
public int ThingId { get; set; }
public string ThingName { get; set; }
public List<Foo> Foos { get; set; }
}
public class Widget
{
public long Sort { get; set; }
public char FirstLetter { get; set; }
}
public class TestMe
{
public Color Color { get; set; }
public long Key { get; set; }
public string Name { get; set; }
public DateTime Created { get; set; }
public DateTime? NCreated { get; set; }
public bool Deleted { get; set; }
public bool? NDeleted { get; set; }
public double Amount { get; set; }
public Thing MyThing { get; set; }
public List<Thing> Things { get; set; }
public List<Widget> Widgets { get; set; }
}
static void Main(string[] args)
{
var testMe = new TestMe
{
Color = Program.Color.Blue,
Key = 3,
Name = "SAK",
Created = new DateTime(2013,10,20,8,0,0),
NCreated = (DateTime?)null,
Deleted = false,
NDeleted = null,
Amount = 13.1313,
MyThing = new Thing(){ThingId=1,ThingName="Thing 1"},
Things = new List<Thing>
{
new Thing
{
ThingId=4,
ThingName="Thing 4",
Foos = new List<Foo>
{
new Foo{FooId=1, FooName="Foo 1"},
new Foo{FooId=2,FooName="Foo2"}
}
},
new Thing
{
ThingId=5,
ThingName="Thing 5",
Foos = new List<Foo>()
}
},
Widgets = new List<Widget>()
};
var objectInitializer = ToObjectInitializer(testMe);
Console.WriteLine(objectInitializer);
// This is the returned C# Object Initializer
var x = new TestMe { Color = Program.Color.Blue, Key = 3, Name = "SAK", Created = new DateTime(2013, 10, 20, 8, 0, 0), NCreated = null, Deleted = false, NDeleted = null, Amount = 13.1313, MyThing = new Thing { ThingId = 1, ThingName = "Thing 1", Foos = new List<Foo>() }, Things = new List<Thing> { new Thing { ThingId = 4, ThingName = "Thing 4", Foos = new List<Foo> { new Foo { FooId = 1, FooName = "Foo 1" }, new Foo { FooId = 2, FooName = "Foo2" } } }, new Thing { ThingId = 5, ThingName = "Thing 5", Foos = new List<Foo>() } }, Widgets = new List<Widget>() };
Console.WriteLine("");
}
public static string ToObjectInitializer(Object obj)
{
var sb = new StringBuilder(1024);
sb.Append("var x = ");
sb = WalkObject(obj, sb);
sb.Append(";");
return sb.ToString();
}
private static StringBuilder WalkObject(Object obj, StringBuilder sb)
{
var properties = obj.GetType().GetProperties();
var type = obj.GetType();
var typeName = type.Name;
sb.Append("new " + type.Name + " {");
bool appendComma = false;
DateTime workDt;
foreach (var property in properties)
{
if (appendComma) sb.Append(", ");
appendComma = true;
var pt = property.PropertyType;
var name = pt.Name;
var isList = property.PropertyType.GetInterfaces().Contains(typeof(IList));
var isClass = property.PropertyType.IsClass;
if (isList)
{
IList list = (IList)property.GetValue(obj, null);
var listTypeName = property.PropertyType.GetGenericArguments()[0].Name;
if (list != null && list.Count > 0)
{
sb.Append(property.Name + " = new List<" + listTypeName + ">{");
sb = WalkList( list, sb );
sb.Append("}");
}
else
{
sb.Append(property.Name + " = new List<" + listTypeName + ">()");
}
}
else if (property.PropertyType.IsEnum)
{
sb.AppendFormat("{0} = {1}", property.Name, property.GetValue(obj));
}
else
{
var value = property.GetValue(obj);
var isNullable = pt.IsGenericType && pt.GetGenericTypeDefinition() == typeof(Nullable<>);
if (isNullable)
{
name = pt.GetGenericArguments()[0].Name;
if (property.GetValue(obj) == null)
{
sb.AppendFormat("{0} = null", property.Name);
continue;
}
}
switch (name)
{
case "Int64":
case "Int32":
case "Int16":
case "Double":
case "Float":
sb.AppendFormat("{0} = {1}", property.Name, value);
break;
case "Boolean":
sb.AppendFormat("{0} = {1}", property.Name, Convert.ToBoolean(value) == true ? "true" : "false");
break;
case "DateTime":
workDt = Convert.ToDateTime(value);
sb.AppendFormat("{0} = new DateTime({1},{2},{3},{4},{5},{6})", property.Name, workDt.Year, workDt.Month, workDt.Day, workDt.Hour, workDt.Minute, workDt.Second);
break;
case "String":
sb.AppendFormat("{0} = \"{1}\"", property.Name, value);
break;
default:
// Handles all user classes, should likely have a better way
// to detect user class
sb.AppendFormat("{0} = ", property.Name);
WalkObject(property.GetValue(obj), sb);
break;
}
}
}
sb.Append("}");
return sb;
}
private static StringBuilder WalkList(IList list, StringBuilder sb)
{
bool appendComma = false;
foreach (object obj in list)
{
if (appendComma) sb.Append(", ");
appendComma = true;
WalkObject(obj, sb);
}
return sb;
}
}
}
I stumbled across this while looking for the same kind of method Matthew described, and was inspired by Evan's answer to write my own extension method. It generates compilable C# code as a string that can be copy/pasted into Visual Studio. I didn't bother with any particular formatting and just output the code on one line and use ReSharper to format it nicely. I've used it with some big DTOs that we were passing around and so far it works like a charm.
Here's the extension method and a couple helper methods:
public static string ToCreationMethod(this object o)
{
return String.Format("var newObject = {0};", o.CreateObject());
}
private static StringBuilder CreateObject(this object o)
{
var builder = new StringBuilder();
builder.AppendFormat("new {0} {{ ", o.GetClassName());
foreach (var property in o.GetType().GetProperties())
{
var value = property.GetValue(o);
if (value != null)
{
builder.AppendFormat("{0} = {1}, ", property.Name, value.GetCSharpString());
}
}
builder.Append("}");
return builder;
}
private static string GetClassName(this object o)
{
var type = o.GetType();
if (type.IsGenericType)
{
var arg = type.GetGenericArguments().First().Name;
return type.Name.Replace("`1", string.Format("<{0}>", arg));
}
return type.Name;
}
The method GetCSharpString contains the logic, and it's open to extension for any particular type. It was enough for me that it handled strings, ints, decimals, dates anything that implements IEnumerable:
private static string GetCSharpString(this object o)
{
if (o is String)
{
return string.Format("\"{0}\"", o);
}
if (o is Int32)
{
return string.Format("{0}", o);
}
if (o is Decimal)
{
return string.Format("{0}m", o);
}
if (o is DateTime)
{
return string.Format("DateTime.Parse(\"{0}\")", o);
}
if (o is IEnumerable)
{
return String.Format("new {0} {{ {1}}}", o.GetClassName(), ((IEnumerable)o).GetItems());
}
return string.Format("{0}", o.CreateObject());
}
private static string GetItems(this IEnumerable items)
{
return items.Cast<object>().Aggregate(string.Empty, (current, item) => current + String.Format("{0}, ", item.GetCSharpString()));
}
I hope someone finds this useful!
It may comes a bit late, but here is my 5cents on that problem.
The mentioned Visual Studio Extension (OmarElabd/ObjectExporter) was a good idea, but I needed to generate C# code from in-memory objects at runtime, during unit test execution. This is what evolved from the original problem: https://www.nuget.org/packages/ObjectDumper.NET/
ObjectDumper.Dump(obj, DumpStyle.CSharp); returns C# initializer code from a variable. Please let me know if you find issues, you might want to report them on github.
There's a solution similar to what Evan proposed, but a bit better suited for my particular task.
After playing a bit with CodeDOM and Reflection it turned out that it would be too complicated in my case.
The object was serialized as XML, so the natural solution was to use XSLT to simply transform it to the object creation expression.
Sure, it covers only certain types of the cases, but maybe will work for someone else.
Here is an update to #revlucio's solution that adds support for booleans and enums.
public static class ObjectInitializationSerializer
{
private static string GetCSharpString(object o)
{
if (o is bool)
{
return $"{o.ToString().ToLower()}";
}
if (o is string)
{
return $"\"{o}\"";
}
if (o is int)
{
return $"{o}";
}
if (o is decimal)
{
return $"{o}m";
}
if (o is DateTime)
{
return $"DateTime.Parse(\"{o}\")";
}
if (o is Enum)
{
return $"{o.GetType().FullName}.{o}";
}
if (o is IEnumerable)
{
return $"new {GetClassName(o)} \r\n{{\r\n{GetItems((IEnumerable)o)}}}";
}
return CreateObject(o).ToString();
}
private static string GetItems(IEnumerable items)
{
return items.Cast<object>().Aggregate(string.Empty, (current, item) => current + $"{GetCSharpString(item)},\r\n");
}
private static StringBuilder CreateObject(object o)
{
var builder = new StringBuilder();
builder.Append($"new {GetClassName(o)} \r\n{{\r\n");
foreach (var property in o.GetType().GetProperties())
{
var value = property.GetValue(o);
if (value != null)
{
builder.Append($"{property.Name} = {GetCSharpString(value)},\r\n");
}
}
builder.Append("}");
return builder;
}
private static string GetClassName(object o)
{
var type = o.GetType();
if (type.IsGenericType)
{
var arg = type.GetGenericArguments().First().Name;
return type.Name.Replace("`1", $"<{arg}>");
}
return type.Name;
}
public static string Serialize(object o)
{
return $"var newObject = {CreateObject(o)};";
}
}
How i can get full right name of generic type?
For example:
This code
typeof(List<string>).Name
return
List`1
instead of
List<string>
How to get a right name?
typeof(List<string>).ToString()
returns System.Collections.Generic.List`1[System.String] but i want to get initial name:
List<string>
Is it real?
Use the FullName property.
typeof(List<string>).FullName
That will give you the namespace + class + type parameters.
What you are asking for is a C# specific syntax. As far as .NET is concerned, this is proper:
System.Collections.Generic.List`1[System.String]
So to get what you want, you'd have to write a function to build it the way you want it. Perhaps like so:
static string GetCSharpRepresentation( Type t, bool trimArgCount ) {
if( t.IsGenericType ) {
var genericArgs = t.GetGenericArguments().ToList();
return GetCSharpRepresentation( t, trimArgCount, genericArgs );
}
return t.Name;
}
static string GetCSharpRepresentation( Type t, bool trimArgCount, List<Type> availableArguments ) {
if( t.IsGenericType ) {
string value = t.Name;
if( trimArgCount && value.IndexOf("`") > -1 ) {
value = value.Substring( 0, value.IndexOf( "`" ) );
}
if( t.DeclaringType != null ) {
// This is a nested type, build the nesting type first
value = GetCSharpRepresentation( t.DeclaringType, trimArgCount, availableArguments ) + "+" + value;
}
// Build the type arguments (if any)
string argString = "";
var thisTypeArgs = t.GetGenericArguments();
for( int i = 0; i < thisTypeArgs.Length && availableArguments.Count > 0; i++ ) {
if( i != 0 ) argString += ", ";
argString += GetCSharpRepresentation( availableArguments[0], trimArgCount );
availableArguments.RemoveAt( 0 );
}
// If there are type arguments, add them with < >
if( argString.Length > 0 ) {
value += "<" + argString + ">";
}
return value;
}
return t.Name;
}
For these types (with true as 2nd param):
typeof( List<string> ) )
typeof( List<Dictionary<int, string>> )
It returns:
List<String>
List<Dictionary<Int32, String>>
In general though, I'd bet you probably don't need to have the C# representation of your code and perhaps if you do, some format better than the C# syntax would be more appropriate.
You could use this:
public static string GetTypeName(Type t) {
if (!t.IsGenericType) return t.Name;
if (t.IsNested && t.DeclaringType.IsGenericType) throw new NotImplementedException();
string txt = t.Name.Substring(0, t.Name.IndexOf('`')) + "<";
int cnt = 0;
foreach (Type arg in t.GetGenericArguments()) {
if (cnt > 0) txt += ", ";
txt += GetTypeName(arg);
cnt++;
}
return txt + ">";
}
For example:
static void Main(string[] args) {
var obj = new Dictionary<string, Dictionary<HashSet<int>, int>>();
string s = GetTypeName(obj.GetType());
Console.WriteLine(s);
Console.ReadLine();
}
Output:
Dictionary<String, Dictionary<HashSet<Int32>, Int32>>
If you have an instance of the list, you can call .ToString() and get the following
System.Collections.Generic.List`1[System.String]
This is in addition to the methods provided by the other answers directly against the type rather than the instance.
Edit: On your edit, I do not believe it is possible without providing your own parsing method, as List<string> is C# shorthand for how the type is implemented, sort of like if you wrote typeof(int).ToString(), what is captured is not "int" but the CTS name, System.Int32.
Here's my implementation, which benefited from #Hans's answer above and #Jack's answer on a duplicate question.
public static string GetCSharpName( this Type type )
{
string result;
if ( primitiveTypes.TryGetValue( type, out result ) )
return result;
else
result = type.Name.Replace( '+', '.' );
if ( !type.IsGenericType )
return result;
else if ( type.IsNested && type.DeclaringType.IsGenericType )
throw new NotImplementedException();
result = result.Substring( 0, result.IndexOf( "`" ) );
return result + "<" + string.Join( ", ", type.GetGenericArguments().Select( GetCSharpName ) ) + ">";
}
static Dictionary<Type, string> primitiveTypes = new Dictionary<Type, string>
{
{ typeof(bool), "bool" },
{ typeof(byte), "byte" },
{ typeof(char), "char" },
{ typeof(decimal), "decimal" },
{ typeof(double), "double" },
{ typeof(float), "float" },
{ typeof(int), "int" },
{ typeof(long), "long" },
{ typeof(sbyte), "sbyte" },
{ typeof(short), "short" },
{ typeof(string), "string" },
{ typeof(uint), "uint" },
{ typeof(ulong), "ulong" },
{ typeof(ushort), "ushort" },
};
Another way to get a nice type name by using an extension:
typeof(Dictionary<string, Dictionary<decimal, List<int>>>).CSharpName();
// output is:
// Dictionary<String, Dictionary<Decimal, List<Int32>>>
The Extension Code:
public static class TypeExtensions
{
public static string CSharpName(this Type type)
{
string typeName = type.Name;
if (type.IsGenericType)
{
var genArgs = type.GetGenericArguments();
if (genArgs.Count() > 0)
{
typeName = typeName.Substring(0, typeName.Length - 2);
string args = "";
foreach (var argType in genArgs)
{
string argName = argType.Name;
if (argType.IsGenericType)
argName = argType.CSharpName();
args += argName + ", ";
}
typeName = string.Format("{0}<{1}>", typeName, args.Substring(0, args.Length - 2));
}
}
return typeName;
}
}
typeof(List<string>).ToString()
An improvement on Adam Sills's answer that works with non-generic nested types, and generic type definitions:
public class TypeNameStringExtensions
{
public static string GetCSharpRepresentation(Type t)
{
return GetCSharpRepresentation(t, new Queue<Type>(t.GetGenericArguments()));
}
static string GetCSharpRepresentation(Type t, Queue<Type> availableArguments)
{
string value = t.Name;
if (t.IsGenericParameter)
{
return value;
}
if (t.DeclaringType != null)
{
// This is a nested type, build the parent type first
value = GetCSharpRepresentation(t.DeclaringType, availableArguments) + "+" + value;
}
if (t.IsGenericType)
{
value = value.Split('`')[0];
// Build the type arguments (if any)
string argString = "";
var thisTypeArgs = t.GetGenericArguments();
for (int i = 0; i < thisTypeArgs.Length && availableArguments.Count > 0; i++)
{
if (i != 0) argString += ", ";
argString += GetCSharpRepresentation(availableArguments.Dequeue());
}
// If there are type arguments, add them with < >
if (argString.Length > 0)
{
value += "<" + argString + ">";
}
}
return value;
}
[TestCase(typeof(List<string>), "List<String>")]
[TestCase(typeof(List<Dictionary<int, string>>), "List<Dictionary<Int32, String>>")]
[TestCase(typeof(Stupid<int>.Stupider<int>), "Stupid<Int32>+Stupider<Int32>")]
[TestCase(typeof(Dictionary<int, string>.KeyCollection), "Dictionary<Int32, String>+KeyCollection")]
[TestCase(typeof(Nullable<Point>), "Nullable<Point>")]
[TestCase(typeof(Point?), "Nullable<Point>")]
[TestCase(typeof(TypeNameStringExtensions), "TypeNameStringExtensions")]
[TestCase(typeof(Another), "TypeNameStringExtensions+Another")]
[TestCase(typeof(G<>), "TypeNameStringExtensions+G<T>")]
[TestCase(typeof(G<string>), "TypeNameStringExtensions+G<String>")]
[TestCase(typeof(G<Another>), "TypeNameStringExtensions+G<TypeNameStringExtensions+Another>")]
[TestCase(typeof(H<,>), "TypeNameStringExtensions+H<T1, T2>")]
[TestCase(typeof(H<string, Another>), "TypeNameStringExtensions+H<String, TypeNameStringExtensions+Another>")]
[TestCase(typeof(Another.I<>), "TypeNameStringExtensions+Another+I<T3>")]
[TestCase(typeof(Another.I<int>), "TypeNameStringExtensions+Another+I<Int32>")]
[TestCase(typeof(G<>.Nested), "TypeNameStringExtensions+G<T>+Nested")]
[TestCase(typeof(G<string>.Nested), "TypeNameStringExtensions+G<String>+Nested")]
[TestCase(typeof(A<>.C<>), "TypeNameStringExtensions+A<B>+C<D>")]
[TestCase(typeof(A<int>.C<string>), "TypeNameStringExtensions+A<Int32>+C<String>")]
public void GetCSharpRepresentation_matches(Type type, string expected)
{
string actual = GetCSharpRepresentation(type);
Assert.AreEqual(expected, actual);
}
public class G<T>
{
public class Nested { }
}
public class A<B>
{
public class C<D> { }
}
public class H<T1, T2> { }
public class Another
{
public class I<T3> { }
}
}
public class Stupid<T1>
{
public class Stupider<T2>
{
}
}
I also chose to forgo his trimArgCount, as I can't see when that would be useful, and to use a Queue<Type> since that was the intent (pulling items off the front while they exist).
I had problems with the other answers in some instances, i.e. with arrays, so I ended up writing yet another one. I don't use the text from Type.Name or similar except to get the plain name of the types, because I don't know if the format is guaranteed to be the same across different .Net versions or with other implementations of the libraries (I assume it isn't).
/// <summary>
/// For the given type, returns its representation in C# code.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="genericArgs">Used internally, ignore.</param>
/// <param name="arrayBrackets">Used internally, ignore.</param>
/// <returns>The representation of the type in C# code.</returns>
public static string GetTypeCSharpRepresentation(Type type, Stack<Type> genericArgs = null, StringBuilder arrayBrackets = null)
{
StringBuilder code = new StringBuilder();
Type declaringType = type.DeclaringType;
bool arrayBracketsWasNull = arrayBrackets == null;
if (genericArgs == null)
genericArgs = new Stack<Type>(type.GetGenericArguments());
int currentTypeGenericArgsCount = genericArgs.Count;
if (declaringType != null)
currentTypeGenericArgsCount -= declaringType.GetGenericArguments().Length;
Type[] currentTypeGenericArgs = new Type[currentTypeGenericArgsCount];
for (int i = currentTypeGenericArgsCount - 1; i >= 0; i--)
currentTypeGenericArgs[i] = genericArgs.Pop();
if (declaringType != null)
code.Append(GetTypeCSharpRepresentation(declaringType, genericArgs)).Append('.');
if (type.IsArray)
{
if (arrayBrackets == null)
arrayBrackets = new StringBuilder();
arrayBrackets.Append('[');
arrayBrackets.Append(',', type.GetArrayRank() - 1);
arrayBrackets.Append(']');
Type elementType = type.GetElementType();
code.Insert(0, GetTypeCSharpRepresentation(elementType, arrayBrackets : arrayBrackets));
}
else
{
code.Append(new string(type.Name.TakeWhile(c => char.IsLetterOrDigit(c) || c == '_').ToArray()));
if (currentTypeGenericArgsCount > 0)
{
code.Append('<');
for (int i = 0; i < currentTypeGenericArgsCount; i++)
{
code.Append(GetTypeCSharpRepresentation(currentTypeGenericArgs[i]));
if (i < currentTypeGenericArgsCount - 1)
code.Append(',');
}
code.Append('>');
}
if (declaringType == null && !string.IsNullOrEmpty(type.Namespace))
{
code.Insert(0, '.').Insert(0, type.Namespace);
}
}
if (arrayBracketsWasNull && arrayBrackets != null)
code.Append(arrayBrackets.ToString());
return code.ToString();
}
I have tested it with crazy types like this, and so far it has worked perfectly:
class C
{
public class D<D1, D2>
{
public class E
{
public class K<R1, R2, R3>
{
public class P<P1>
{
public struct Q
{
}
}
}
}
}
}
type = typeof(List<Dictionary<string[], C.D<byte, short[,]>.E.K<List<int>[,][], Action<List<long[][][,]>[], double[][,]>, float>.P<string>.Q>>[][,][,,,][][,,]);
// Returns "System.Collections.Generic.List<System.Collections.Generic.Dictionary<System.String[],Test.Program.C.D<System.Byte,System.Int16[,]>.E.K<System.Collections.Generic.List<System.Int32>[,][],System.Action<System.Collections.Generic.List<System.Int64[][][,]>[],System.Double[][,]>,System.Single>.P<System.String>.Q>>[][,][,,,][][,,]":
GetTypeCSharpRepresentation(type);
There may still be some gotchas I didn't think about, but there's a known one: to retrieve the names, I only get characters that meet the condition char.IsLetterOrDigit(c) || c == '_' until one that doesn't is found, so any names of types that use allowed characters that don't meet the condition will fail.
Came across this and thought I'd share my own solution. It handles multiple generic arguments, nullables, jagged arrays, multidimensional arrays, combinations of jagged/multidimensional arrays, and any nesting combinations of any of the above. I use it mainly for logging so that it's easier to identify complicated types.
public static string GetGoodName(this Type type)
{
var sb = new StringBuilder();
void VisitType(Type inType)
{
if (inType.IsArray)
{
var rankDeclarations = new Queue<string>();
Type elType = inType;
do
{
rankDeclarations.Enqueue($"[{new string(',', elType.GetArrayRank() - 1)}]");
elType = elType.GetElementType();
} while (elType.IsArray);
VisitType(elType);
while (rankDeclarations.Count > 0)
{
sb.Append(rankDeclarations.Dequeue());
}
}
else
{
if (inType.IsGenericType)
{
var isNullable = inType.IsNullable();
var genargs = inType.GetGenericArguments().AsEnumerable();
var numer = genargs.GetEnumerator();
numer.MoveNext();
if (!isNullable) sb.Append($"{inType.Name.Substring(0, inType.Name.IndexOf('`'))}<");
VisitType(numer.Current);
while (numer.MoveNext())
{
sb.Append(",");
VisitType(numer.Current);
}
if (isNullable)
{
sb.Append("?");
}
else
{
sb.Append(">");
}
}
else
{
sb.Append(inType.Name);
}
}
}
VisitType(type);
var s = sb.ToString();
return s;
}
This:
typeof(Dictionary<int?, Tuple<string[], List<string[][,,,]>>>).GetGoodName()
...returns this:
Dictionary<Int32?,Tuple<String[],List<String[][,,,]>>>
If you want the base generic type used:
List<string> lstString = new List<string>();
Type type = lstString.GetType().GetGenericTypeDefinition();
Assuming that you want to use the type do do something and that you don't really need the actual string definition which isn't all that useful.