我知道标题有点模糊。但我想要实现的是这样的:
在抽象类中:
public abstract bool TryGet<T>(string input, out T output) where T : struct;
在具有此签名的类中:
private class Param<T> : AbstractParam where T : struct
此实施:
public override bool TryGetVal<TOriginal>(string input, out TOriginal output)
{
T oTemp;
bool res = _func(input, out oTemp); // _func is the actual function
// that retrieves the value.
output = (TOriginal)oTemp; // Compile-time error
return res;
}
TOriginal
将始终与T
的类型相同。这会绕过编译时错误,但我不想这样做性能打击:
output = (TOriginal)(object)oTemp;
如果它是引用类型,则会提供解决方案:
output = oTemp as TOriginal;
反射/动态也可以解决问题,但性能影响更大:
output = (TOriginal)(dynamic)oTemp;
我尝试使用不安全的代码,但没有成功,但那可能只是我。
所以我最好的希望是编译器优化(TOriginal)(object)oTemp
到(TOriginal)oTemp
我不知道。或者说这是一个不安全的方法。
请告诉我关于过早优化的讲座,我想知道这纯粹是为了研究,如果有办法克服这个限制,我感兴趣。我意识到这对实际表现的影响可以忽略不计。
最终结论:
在拆解情况后,结果如下:
return (TOut)(object)_value;
00000000 push ebp
00000001 mov ebp,esp
00000003 push eax
00000004 mov dword ptr [ebp-4],ecx
00000007 cmp dword ptr ds:[003314CCh],0
0000000e je 00000015
00000010 call 61A33AD3
00000015 mov eax,dword ptr [ebp-4]
00000018 mov eax,dword ptr [eax+4]
0000001b mov esp,ebp
0000001d pop ebp
0000001e ret
return _value;
00000000 push ebp
00000001 mov ebp,esp
00000003 push eax
00000004 mov dword ptr [ebp-4],ecx
00000007 cmp dword ptr ds:[004814B4h],0
0000000e je 00000015
00000010 call 61993AA3
00000015 mov eax,dword ptr [ebp-4]
00000018 mov eax,dword ptr [eax+4]
0000001b mov esp,ebp
0000001d pop ebp
0000001e ret
原来今天的编译器对此进行了优化,因此没有性能成本。
output = (TOriginal)(object)oTemp;
这是执行此操作的最佳方式:)。
关于参考类型的说明:
删除struct
约束并传递引用类型(在我的情况下为string
)时,此优化是 NOT 。
结果:
return (TOut)(object)_value;
00000000 push ebp
00000001 mov ebp,esp
00000003 sub esp,10h
00000006 mov dword ptr [ebp-4],edx
00000009 mov dword ptr [ebp-10h],ecx
0000000c mov dword ptr [ebp-8],edx
0000000f cmp dword ptr ds:[003314B4h],0
00000016 je 0000001D
00000018 call 61A63A43
0000001d mov eax,dword ptr [ebp-8]
00000020 mov eax,dword ptr [eax+0Ch]
00000023 mov eax,dword ptr [eax]
00000025 mov dword ptr [ebp-0Ch],eax
00000028 test dword ptr [ebp-0Ch],1
0000002f jne 00000036
00000031 mov ecx,dword ptr [ebp-0Ch]
00000034 jmp 0000003C
00000036 mov eax,dword ptr [ebp-0Ch]
00000039 mov ecx,dword ptr [eax-1]
0000003c mov eax,dword ptr [ebp-10h]
0000003f mov edx,dword ptr [eax+4]
00000042 call 617D79D8
00000047 mov esp,ebp
00000049 pop ebp
0000004a ret
return _value;
00000000 push ebp
00000001 mov ebp,esp
00000003 push eax
00000004 mov dword ptr [ebp-4],ecx
00000007 cmp dword ptr ds:[003314B4h],0
0000000e je 00000015
00000010 call 61A639E3
00000015 mov eax,dword ptr [ebp-4]
00000018 mov eax,dword ptr [eax+4]
0000001b mov esp,ebp
0000001d pop ebp
0000001e ret
如果你想要一种廉价的方式来投射'没有正确的类型检查',as
运算符就是你的解决方案。
答案 0 :(得分:6)
我打算给你那个你不想要的讲座,因为你显然不理解。
“测量,测量,测量”有两个原因! (或等效地“Profile,Profile,Profile!”)优化方法:
将努力放在影响最大的地方。这就是“过早优化”一词的用武之地。
有时这个理由不适用(当你想了解理论/出于学术原因时)。
找出哪个实施更快。
现代CPU是复杂的野兽,甚至比较两个不同的机器代码序列也无法显示哪个更好,这是由于缓存行为的复杂性,管道数据依赖性,微代码等等。你正在运行比这高两级(C#代码 - &gt; MSIL - &gt;机器代码)。没有测量就无法确定优化程度。
你说:
这会绕过编译时错误,但我不想这样做会导致性能下降:
output = (TOriginal)(object)oTemp;
但我认为那里确实没有任何表现。对于每种值类型,通用方法都是JITted,该过程应该完全消除任何想象的性能损失。但是,欢迎您通过性能数据(实际分析器测量)或至少反汇编JIT生成的机器代码来证明实际存在成本。
在这种特殊情况下,如果它们始终与您声明的类型相同,则不清楚为什么要开始使用两个不同的泛型类型参数。只需删除TOriginal
并使用T
作为输出参数的类型。
答案 1 :(得分:4)
我觉得具有讽刺意味的是,本的回答说“使用科学:衡量它来发现”,“这是我对真实情况的看法”:
我认为那里确实没有任何表现。对于每种值类型,通用方法都是JITted,该过程应该完全消除任何想象的性能损失。
根据更新后的问题中显示的实际反汇编,原始海报声称使用的抖动显然至少在某些时候进行了优化。我没有分析这个说法是否正确;我想实际看到正在编译的实际代码,IL和生成的程序集,以了解这里发生了什么。
在我过去对这个领域的调查中,我发现了许多情况,其中验证者和抖动都不够聪明,特别是在消除拳击处罚方面。这些是否全部被淘汰我不知道。
如果其中一些已被淘汰,那么我很高兴了解到这一点。
因此,您无法断定表面抖动执行或不执行消除装箱的优化。我见过没有的情况;我们在这里有一个未经证实的声明,在某些情况下确实如此。
Ben继续提出一些好建议:
但是,欢迎您通过性能数据(实际分析器测量)或至少反汇编JIT生成的机器代码来证明实际存在成本。
实际上,我强烈建议您这样做,并且不止一次抖动。
让我们重新开始并实际回答所提出的问题。我们应该首先简化和澄清所描述的案例:
abstract class B
{
public abstract T M<T>() where T : struct;
}
private class D<U> : B where U : struct
{
public override V M<V>()
{
U u = default(U);
return (V)u; // compile-time error
}
}
原始海报说V将永远与U相同。这是第一个问题。为什么?没有任何东西阻止用户在M<bool>
的实例上调用D<double>
。类型检查器完全正确,注意到可能没有从U到V的转换。
正如原始海报所说,你可以通过装箱和拆箱来绕类型检查器进行最终运行:
return (V)(object)u; // Runtime error, not compile-time error
接下来的问题是“在没有崩溃并且在运行时可怕地死亡的情况下,抖动会消除拳击惩罚吗?”
抖动仅对方法进行一次jits并共享引用类型参数的代码,但每次都为不同的值类型参数重新匹配它。因此,当为U和V提供的特定参数是相同的值类型时,有机会消除惩罚。
我几年前曾想过自己和所以我检查了。我没有检查更多最近的抖动版本,但上次我检查拳击处罚是不消除。抖动分配内存,将值复制到堆中,然后再将其复制回来。
显然,根据更新的问题,情况已不再如此;现在测试的抖动执行此优化。就像我说的那样,我自己没有证实这个说法。
允许允许进行优化,但是上次检查时,实际上它没有,所以我们知道在野外至少有一个抖动没有这种优化。
一个更有趣的例子是实际 约束为相等的类型参数:
abstract class E<T>
{
public abstract U M<U>(T t) where U : T;
}
class F<V> : E<V> where V : struct
{
public override W M<W>(V v)
{
return v; // Error
}
}
同样,这是非法的,即使C#编译器可以逻辑推断W现在必须与V相同。
您可以再次引入强制转换以解决问题,但 IL验证程序的类型分析器要求将V框装箱并取消装箱为W。
再次,抖动可以推断出拳击和拆箱是无操作,并消除它,但我最后一次检查它没有。它现在可能;试一试,看看。
我报告说这是对抖动团队的可能优化;他们告诉我他们有更高的优先级,这是一个非常合理的回应。这是一个模糊且不太可能出现的情况,也不是我要优先考虑的情况。
如果事实上现在已经进行了这种优化,那么我很惊喜。
答案 2 :(得分:0)
您希望避免将T / TOriginal值转换为对象,这会导致出现装箱问题,其中值类型(所有结构都是)将被封装为堆上的System.Object。有几种方法可以解决铸造问题。最简单的方法是让抽象类包含类级泛型类型参数而不是TryGet
方法,如:
public abstract class AbstractParam<T> where T : struct
{
//....
public abstract bool TryGet(string input, out T output);
}
另一个选项是投放到Nullable<TOriginal>
,然后像这样调用GetValueOrDefault()
:
public override bool TryGet<TOriginal>(string input, out TOriginal output)
{
T oTemp;
bool res = _func(input, out oTemp);
Nullable<TOriginal> n = oTemp as Nullable<TOriginal>;
output = n.GetValueOrDefault();
return res;
}