I have two arrays in my Base class, and I want to create Indexers that can be used in both of them, attached below is an MVCE of what I am trying to do.
class Indexer
{
private string[] namelist = new string[size];
private char[] grades = new string[size];
static public int size = 10;
public IndexedNames() {
for (int i = 0; i < size; i++){
namelist[i] = "N. A.";
grades[i] = 'F';
}
}
public string this[int index] {
get {
string tmp;
if( index >= 0 && index <= size-1 ) {
tmp = namelist[index];
} else {
tmp = "";
}
return ( tmp );
}
set {
if( index >= 0 && index <= size-1 ) {
namelist[index] = value;
}
}
}
In the above coed if you comment out the lines private char[] grades = new string[size]; and grades[i] = 'F'; then you can use the indexers as object_name[i] but I want to be able to access both namelist and grades by indexers.
Note : I cannot use structures to wrap them together as in my application, there size may not always be same.
Is this possible or I would need to go around with some hack.
Edit
I am looking for something like names.namelist[i] and names.grades[i], or some statements that I can access them separately. Also Indexer logic is not consistent, and even size varies in some arrays, that was skipped here to aid simplicity in MVCE.
Sorry, no-can-do.
Although Indexers can be Overloaded and can have more than one formal parameter, you can't make two variations based on the same Parameter in the same class. This is a Language Limitation (or blessing).
Indexers (C# Programming Guide)
However, this should lead you to several options.
You can just make use of C#7. Ref returns
Starting with C# 7.0, C# supports reference return values (ref
returns). A reference return value allows a method to return a
reference to a variable, rather than a value, back to a caller. The
caller can then choose to treat the returned variable as if it were
returned by value or by reference. The caller can create a new
variable that is itself a reference to the returned value, called a
ref local.
public ref string Namelist(int position)
{
if (array == null)
throw new ArgumentNullException(nameof(array));
if (position < 0 || position >= array.Length)
throw new ArgumentOutOfRangeException(nameof(position));
return ref array[position];
}
...
// Which allows you to do funky things like this, etc.
object.NameList(1) = "bob";
You could make sub/nested classes with indexers
That's to say, you could create a class that has the features you need with indexers, and make them properties of the main class. So you get something like you envisaged object.Namelist[0] and object.Grades[0].
Note : in this situation you could pass the arrays down as references and still access them in the main array like you do.
Example which includes both:
Given
public class GenericIndexer<T>
{
private T[] _array;
public GenericIndexer(T[] array)
{
_array = array;
}
public T this[int i]
{
get => _array[i];
set => _array[i] = value;
}
}
Class
public class Bobo
{
private int[] _ints = { 2, 3, 4, 5, 5 };
private string[] _strings = { "asd","asdd","sdf" };
public Bobo()
{
Strings = new GenericIndexer<string>(_strings);
Ints = new GenericIndexer<int>(_ints);
}
public GenericIndexer<string> Strings ;
public GenericIndexer<int> Ints ;
public void Test()
{
_ints[0] = 234;
}
public ref int DoInts(int pos) => ref _ints[pos];
public ref string DoStrings(int pos) => ref _strings[pos];
}
Usage:
var bobo = new Bobo();
bobo.Ints[1] = 234;
bobo.DoInts(1) = 42;
I think only a two parameter indexer can achieve what you want.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace ConsoleApp1
{
class MyClass
{
protected static Dictionary<string, FieldInfo[]> table = new Dictionary<string, FieldInfo[]>();
static public int size = 10;
protected char[] grades = new char[size];
public object this[string name, int index]
{
get
{
var fieldInfos = table[this.GetType().FullName];
return ((Array)fieldInfos.First((x) => x.Name == name).GetValue(this)).GetValue(index);
}
set
{
var fieldInfos = table[this.GetType().FullName];
((Array)fieldInfos.First((x) => x.Name == name).GetValue(this)).SetValue(value, index);
}
}
static void Main()
{
var names = new MyChildClass();
names[DataColumns.Grades, 1] = 'S';
names[DataColumns.NameList, 9] = "W.S";
}
}
class MyChildClass : MyClass
{
private string[] namelist = new string[size];
static MyChildClass()
{
var t = typeof(MyChildClass);
table.Add(t.FullName, t.GetFields(BindingFlags.NonPublic | BindingFlags.Instance));
}
public MyChildClass()
{
for (int i = 0; i < size; i++)
{
namelist[i] = "N. A.";
grades[i] = 'F';
}
}
}
static class DataColumns
{
public static string NameList = "namelist";
public static string Grades = "grades";
}
}
Maybe something like this:
class Indexer
{
private string[] namelist = new string[size];
private string[] grades = new string[size + 1]; // size +1 to indicate different
// size
static public int size = 10;
public void IndexedNames()
{
for (int i = 0; i < size; i++)
{
namelist[i] = "N. A.";
grades[i] = "F";
}
}
public string this[int i, int j]
{
get
{
string tmp;
// we need to return first array
if (i > 0)
{
tmp = namelist[i];
}
else
{
tmp = grades[i];
}
return (tmp);
}
set
{
if (i > 0)
{
namelist[i] = value;
}
else grades[i] = value;
}
}
}
Consider the following Enum:
Enum AnimalType
{
Dog = 1,
Cat = 2,
Bird = 4,
Wolf = 8
}
Now suppose we want to find all possible flag combinations where Dog is active, for example.
I divised the following method to do this:
public static int[] testss(int value)
{
var animalTypes = (AnimalType)value;
List<int> possibleValues = new List<int>();
possibleValues.Add(value);
int totalEnumValues = Enum.GetNames(typeof(AnimalType)).Length;
List<int> helper = new List<int>();
int cnt = 0;
for (int i = 1; i < totalEnumValues; i++)
{
foreach (Enum val in Enum.GetValues(animalTypes.GetType()))
{
if (cnt >= i)
break;
if (i == 1)
{
if ((AnimalType)val != (AnimalType)value)
{
possibleValues.Add((int)(AnimalType)val + value);
}
}
else
{
if ((AnimalType)val != (AnimalType)value && (cnt < i))
{
helper.Add((int)(AnimalType)val);
cnt += 1;
}
}
}
if (cnt > 0)
{
possibleValues.Add(helper.Sum() + value);
helper = new List<int>();
cnt = 0;
}
}
return possibleValues.ToArray();
}
This method will build an array with all the possible numeric representations containing a given flag as input.
It works only partially, if you test it for Dog (1) for example, you'll see that 2 values are missing from the possibleValues array.
Can you help me realizing where I went wrong?
You could use the [Flags] enum attribute, then an extension method. Something like this:
[Flags]
enum AnimalType
{
Dog = 1,
Cat = 2,
Bird = 4,
Wolf = 8
}
static class Program
{
static void Main(string[] args)
{
var test = AnimalType.Dog.AllContaining();
}
public static int[] AllContaining(this AnimalType thisAnimal)
{
List<int> retVal = new List<int>();
var possibleEnums = Enum.GetValues(typeof(AnimalType)).Length;
var maxValue = (int)Math.Pow(2, possibleEnums);
for (int i = 0; i < maxValue; i++)
{
if (((AnimalType)i).HasFlag(thisAnimal))
{
retVal.Add(i);
}
}
return retVal.ToArray();
}
}
It spins through all possible integer values of the enum, and sees if the 'flag' for the supplied animal is present. If it is, it adds to the return array.
Assuming there's not a lot of enum values, you can try:
private static void Main(string[] args)
{
var type = AnimalType.Wolf;
foreach (var x in GetAllPossibleCombinationWith(type))
{
Console.WriteLine(x);
}
Console.ReadLine();
}
public static IEnumerable<AnimalType> GetAllPossibleCombinationWith(AnimalType type) // Bird
{
var maxValue = Enum.GetValues(typeof(AnimalType)).Cast<int>().Max();
var combinationValue =2* maxValue - 1;
for (int i = 0; i < combinationValue; i++)
{
var val = (AnimalType) i;
if ((val & type) == type) yield return val;
}
}
[Flags]
public enum AnimalType
{
Dog = 1,
Cat = 2,
Bird = 4,
Wolf = 8,
Fish = 16
}
It assumes there's no "hole" in the flags values.
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 need a ring list dictionary that can store key and item. Capacity = 50 and when I add #51 the first item must be removed. Basically it must be a dictionary that behaves like a ring list.
Is there something in .NET Framework that can do that ? Or do I have to write it by myself ?
You won't find anything built-in I think but you can easily implement one using OrderedDictionary
OrderedDictionary maintains items in order which they are inserted. Whenever you reach the limit/capacity you can remove the first item.
or use an extension method :
EDIT :
because
latest added entry ends up being returned first.
so
u can remove the first item like :
dictionary.Remove(dictionary.Last().Key);
& so your extension method is :
addExtension(this Dictionary<string, object> dictionary, string key, object value)
{
if(dictionary.Count == 50)
dictionary.Remove(dictionary.Last().Key);
dictionary.Add(key, value);
}
Try this:
class Program
{
static void Main(string[] args)
{
var rD = new RingDictionary(50);
for (int i = 0; i < 75; i++)
{
rD.Add(i, i);
}
foreach (var item in rD.Keys)
{
Console.WriteLine("{0} {1}", item, rD[item]);
}
}
}
class RingDictionary : OrderedDictionary
{
int indexKey;
int _capacity = 0;
public int Capacity
{
get { return _capacity; }
set
{
if (value <= 0)
{
var errorMessage = typeof(Environment)
.GetMethod(
"GetResourceString",
System.Reflection.BindingFlags.Static |
System.Reflection.BindingFlags.NonPublic,
null,
new Type[] { typeof(string) },
null)
.Invoke(null, new object[] {
"ArgumentOutOfRange_NegativeCapacity"
}).ToString();
throw new ArgumentException(errorMessage);
}
_capacity = value;
}
}
public RingDictionary(int capacity)
{
indexKey = -1;
Capacity = capacity;
}
public new void Add(object key, object value)
{
indexKey++;
if (base.Keys.Count > _capacity)
{
for (int i = base.Keys.Count-1; i >Capacity-1 ; i--)
{
base.RemoveAt(i);
}
}
if (base.Keys.Count == _capacity)
{
base.RemoveAt(indexKey % _capacity);
base.Insert(indexKey % _capacity, key, value);
}
else
{
base.Add(key, value);
}
}
}
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.