Хочу соорудить свою реализацию Type.FullName
| Вот мой страшный вариант (пока лучше не смотреть) |
| //-----------------------------------------------------------------------
public static string Extension__BuildHumanName(this Type type)
{
Debug.Assert(!type.IsGenericParameter);
var ctx
=new tagBuildHumanNameCtx
(type);
var result
=Helper__BuildHumanName
(ref ctx,
type);
return result;
}//Extension__BuildHumanName
//-----------------------------------------------------------------------
private struct tagBuildHumanNameCtx
{
public int iGenericArg;
public readonly Type[] GenericArguments;
public tagBuildHumanNameCtx(Type type)
{
this.iGenericArg=0;
if(!type.IsGenericType)
this.GenericArguments=null;
else
this.GenericArguments=type.GetGenericArguments();
}//tagCTX
};//struct tagBuildHumanNameCtx
//-----------------------------------------------------------------------
private static string Helper__BuildHumanName(ref tagBuildHumanNameCtx ctx,
Type type)
{
Debug.Assert(!Object.ReferenceEquals(type,null));
var sb
=new System.Text.StringBuilder();
//------------------------------------------------------- ARRAY
if(type.IsArray)
{
var elementType
=type.GetElementType();
Debug.Assert(!Object.ReferenceEquals(elementType,null));
sb.Append
(Extension__BuildHumanName(elementType));
var arrayRank
=type.GetArrayRank();
Debug.Assert(arrayRank>0);
for(int i=0;i!=arrayRank;++i)
sb.Append("[]");
return sb.ToString();
}//if type.IsArray
Debug.Assert(!type.IsArray);
//------------------------------------------------------- STD
//------------------------- parent class OR namespace
if(!Object.ReferenceEquals(type.DeclaringType,null))
{
var declaringTypeSign
=Helper__BuildHumanName
(ref ctx,
type.DeclaringType);
Debug.Assert(!string.IsNullOrEmpty(declaringTypeSign));
sb.Append(declaringTypeSign);
sb.Append('+');
}
else
{
for(;;)
{
if(Helper__BuildHumanName__SkipNms(type))
break;
var nms
=type.Namespace;
if(Object.ReferenceEquals(nms,null))
break;
Debug.Assert(nms.Length>0);
if(nms.Length==0)
break;
sb.Append(nms);
sb.Append('.');
break;
}//for[ever]
}//else
//------------------------- name
if(!type.IsGenericType)
{
sb.Append(Helper__CheckTypeName(type.Name));
}
else
{
Debug.Assert(type.IsGenericType);
Debug.Assert(!Object.ReferenceEquals(ctx.GenericArguments,null));
Debug.Assert(ctx.iGenericArg<=ctx.GenericArguments.Length);
//-------------------------
var extractResult
=Helper__ExtractGenericName(type);
sb.Append(extractResult.Name);
if(!extractResult.HasGenericParams)
{
//NO GENERIC PARAMETERS
}
else
{
//HAS GENERIC PARAMETERS
sb.Append('<');
var cGenericArgs
=type.GetGenericArguments().Length;
Debug.Assert(cGenericArgs>0);
Debug.Assert(cGenericArgs<=ctx.GenericArguments.Length);
Debug.Assert(ctx.iGenericArg<=cGenericArgs);
bool isFirst
=true;
for(;ctx.iGenericArg!=cGenericArgs;++ctx.iGenericArg,isFirst=false)
{
Debug.Assert(ctx.iGenericArg<ctx.GenericArguments.Length);
var a
=ctx.GenericArguments[ctx.iGenericArg];
Debug.Assert(!Object.ReferenceEquals(a,null));
if(!isFirst)
sb.Append(',');
if(!a.IsGenericParameter)
{
if(!isFirst)
sb.Append(' ');
sb.Append
(Extension__BuildHumanName(a));
}//if !a.IsGenericParameter
}//for i
sb.Append('>');
}//if extractResult.Item1
}//else type.IsGenericType with parameters
//-------------------------- Go home...
return sb.ToString();
}//Helper__BuildHumanName
//-----------------------------------------------------------------------
private static bool Helper__BuildHumanName__SkipNms(Type type)
{
Debug.Assert(!Object.ReferenceEquals(type,null));
if(!type.IsGenericType)
return false;
if(!type.IsGenericTypeDefinition)
type=type.GetGenericTypeDefinition();
//special support for Nullable - skip namespace
if(type==Structure_TypeCache.TypeOf__System_Nullable)
return true;
return false;
}//Helper__BuildHumanName__SkipNms
//-----------------------------------------------------------------------
private struct tagResultOfExtractGenericName
{
public bool HasGenericParams;
public string Name;
public tagResultOfExtractGenericName(bool hasGenericParams,string name)
{
this.HasGenericParams=hasGenericParams;
this.Name=name;
}//tagResultOfExtractGenericName
};//class tagResultOfExtractGenericName
//-----------------------------------------------------------------------
private static tagResultOfExtractGenericName Helper__ExtractGenericName(Type type)
{
Debug.Assert(!Object.ReferenceEquals(type,null));
Debug.Assert(type.IsGenericType);
//Example: "Nullable`1"
var name
=Helper__CheckTypeName(type.Name);
var i
=name.IndexOf('`');
if(i==-1)
return new tagResultOfExtractGenericName(false,name);
Debug.Assert(i>0);
var name2
=name.Substring(0,i);
return new tagResultOfExtractGenericName(true,Helper__CheckTypeName(name2));
}//Helper__ExtractGenericName
//-----------------------------------------------------------------------
private static string Helper__CheckTypeName(string name)
{
Debug.Assert(!string.IsNullOrEmpty(name));
if(Object.ReferenceEquals(name,null))
return "##NULL_TYPE_NAME";
if(name.Length==0)
return "##EMPTY_TYPE_NAME";
return name;
}//Helper__CheckTypeName
|
| |
Вот тесты, которые отрабатывают:
static class tagCLASS09_00
{
static public class tagCLASS09_01<T1>
{
static public class tagCLASS09_02
{
static public class tagCLASS09_03<T2,T3>
{
public static T1 EXEC(T1 v)
{
return v;
}//EXEC
}//class tagCLASS09_03<T2,T3>
}//class tagCLASS09_02
}//class tagCLASS09_01<T>
}//class tagCLASS09_00
//-----------------------------------------------------------------------
[Test]
public static void Test_09__nested_01_spec()
{
Assert.AreEqual
("TestsFor__Extension__BuildHumanName+tagCLASS09_00+tagCLASS09_01<System.Int32>+tagCLASS09_02+tagCLASS09_03<System.Int16, System.String>",
typeof(tagCLASS09_00.tagCLASS09_01<int>.tagCLASS09_02.tagCLASS09_03<short,string>).Extension__BuildHumanName());
}//Test_09__nested_01_spec
//-----------------------------------------------------------------------
[Test]
public static void Test_09__nested_02_gen()
{
Assert.AreEqual
("TestsFor__Extension__BuildHumanName+tagCLASS09_00+tagCLASS09_01<>+tagCLASS09_02+tagCLASS09_03<,>",
typeof(tagCLASS09_00.tagCLASS09_01<>.tagCLASS09_02.tagCLASS09_03<,>).Extension__BuildHumanName());
}//Test_09__nested_02_gen
Проблема с вложенным классом tagCLASS09_02. Он generic,
но без параметров.
Как мне это (то, что он без параметров) по-человечески определить через свойства System.Type?
Сейчас я смотрю на Type.Name и если в нем есть символ '`', то значит параметры есть, а если нет — значит нет.
Этот изврат выполняется в методе Helper__ExtractGenericName.
Но это как-то через .... пятую точку.
Трассировка работы тестов дает такие данные:
Test_09__nested_01_spec:
type.Name: "tagCLASS09_01`1" type.GenericTypeArguments: 0 type.GenericTypeParameters: 1
type.Name: "tagCLASS09_02" type.GenericTypeArguments: 0 type.GenericTypeParameters: 1
type.Name: "tagCLASS09_03`2" type.GenericTypeArguments: 3 type.GenericTypeParameters: 0
Test_09__nested_02_gen:
type.Name: "tagCLASS09_01`1" type.GenericTypeArguments: 0 type.GenericTypeParameters: 1
type.Name: "tagCLASS09_02" type.GenericTypeArguments: 0 type.GenericTypeParameters: 1
type.Name: "tagCLASS09_03`2" type.GenericTypeArguments: 0 type.GenericTypeParameters: 3
Не понятно, на что тут, кроме апострофа в Type.Name, можно опереться.
Не подскажите, где можно украсть правильное решение?
--- [пытался копать реализацию Type.Name]
Отладчик показывает RuntimeType.CoreCLR.cs
public override string Name => GetCachedName(TypeNameKind.Name)!;
// This method looks like an attractive inline but expands to two calls,
// neither of which can be inlined or optimized further. So block it
// from inlining.
[MethodImpl(MethodImplOptions.NoInlining)]
private string? GetCachedName(TypeNameKind kind) => Cache.GetName(kind);
Ну а дальше там дебри...
private string ConstructName([NotNull] ref string? name, TypeNameFormatFlags formatFlags) =>
name ??= new RuntimeTypeHandle(m_runtimeType).ConstructName(formatFlags);
[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
private static extern void ConstructName(QCallTypeHandle handle, TypeNameFormatFlags formatFlags, StringHandleOnStack retString);
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Здравствуйте, Danchik, Вы писали:
КД>>Не подскажите, где можно украсть правильное решение?
D>https://github.com/linq2db/linq2db/blob/master/Source/LinqToDB/Common/Internal/TypeExtensions.cs#L39
var genericPartIndex = type.Name.IndexOf('`');
if (genericPartIndex <= 0)
{
builder.Append(type.Name);
return;
}
Не, ну это как у меня. Не
честно интересно.
Кста, меня терзают смутные сомнения насчет правильности обработки нулевого значения.
---
Я тут ночью проснулся и подумал — "дурень я дурень, надо было другие данные смотреть"
Test_09__nested_01_spec:
type.Name: "tagCLASS09_01`1" type.GenericTypeArguments: 0 type.GenericTypeParameters: 1 type.GetGenericArguments: 1
type.Name: "tagCLASS09_02" type.GenericTypeArguments: 0 type.GenericTypeParameters: 1 type.GetGenericArguments: 1
type.Name: "tagCLASS09_03`2" type.GenericTypeArguments: 3 type.GenericTypeParameters: 0 type.GetGenericArguments: 3
Test_09__nested_02_gen:
type.Name: "tagCLASS09_01`1" type.GenericTypeArguments: 0 type.GenericTypeParameters: 1 type.GetGenericArguments: 1
type.Name: "tagCLASS09_02" type.GenericTypeArguments: 0 type.GenericTypeParameters: 1 type.GetGenericArguments: 1
type.Name: "tagCLASS09_03`2" type.GenericTypeArguments: 0 type.GenericTypeParameters: 3 type.GetGenericArguments: 3
Смотрим сверху вниз — от корневого класса к вложенному.
Количество параметров generic-типа — это разница текущего и предыдущего значения GetGenericArguments().Length.
Или, другими словами this.GetType().GetGenericArguments().Length — this.DeclaringType().GetGenericArguments().Length
Это для этого, конкретного случая, когда нет наследования generic-интерфейсов.
А если еще наследуются generic-интерфейсы, то наверное надо учитывать GetGenericArguments().Length реализуемых интерфейсов.
Там наверное надо выполнять обход реализуемых интерфейсов, чтобы правильно вычислять индекс первого аргумента (offset) в type.GetGenericArguments() — tagBuildHumanNameCtx.iGenericArg.
UPD
Это я не туда стал думать. Интерфейсы наследуются, а тут у нас вложенные конструкции.
Так что тут все норм.
Идея с "разница текущего и предыдущего значения GetGenericArguments().Length" работает на ура.
Там правда ничего вычитать не надо. Надо просто запоминать сколько generic параметров у охватывающего класса. Если оно равно текущему — значит generic параметров нет.
---
Все перерыл — не нашел где в Type можно взять "чистое" имя generic типа без этого '`'. Походу такого свойства нет.
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Здравствуйте, Коваленко Дмитрий, Вы писали:
КД>[cut=Вот мой страшный вариант (пока лучше не смотреть)]
PasteBin'у не учили в детстве? ЗДЕСЬ эта простыня зачем?
КД>Вот тесты, которые отрабатывают:
КД> static public class tagCLASS09_02
КД>Проблема с вложенным классом tagCLASS09_02. Он generic, но без параметров.
Вот здесь вообще не понял. Что в нём "generic", если нет параметров??
Здравствуйте, Kolesiki, Вы писали:
K>Вот здесь вообще не понял. Что в нём "generic", если нет параметров??
public class Tree<T>
{
public class Node
{}
}