提问人:soulshined 提问时间:11/9/2023 最后编辑:soulshined 更新时间:11/10/2023 访问量:77
具有重载运算符的 Hashtable 子类
Hashtable Subclass With Overloaded Operators
问:
以 c# 中创建的 Hashtable 子类为例:
public class CustomHashtable : Hashtable
{
public CustomHashtable() : base() { }
public CustomHashtable(Hashtable a) : base(a) {}
public string GetFoo()
{
return "bar";
}
public static CustomHashtable operator +(CustomHashtable a, Hashtable b)
{
foreach (DictionaryEntry entry in b)
{
a.Add(entry.Key, entry.Value + "z");
}
return a;
}
}
将其作为 csharp 控制台应用程序运行时,您会获得相应的类型化重载:
var ch = new CustomHashtable(new Hashtable(){
{ "Age", 54 }
});
ch += new Hashtable(){
{ "LastName", "Jobs" },
{ "FirstName", "Steve"}
};
System.Console.WriteLine(ch.GetType()); // CustomHashtable
System.Console.WriteLine(ch.GetFoo()); // bar
但是,在 PowerShell 中导入此方法时,我收到一条错误消息,指出该方法不存在:System.Collections.Hashtable
Import-Module "$PSScriptRoot/CustomHashtable.dll"
$Hash = [CustomHashtable]::New(@{
Foo = 'Bar';
})
$Hash += @{
Name = 'Toot';
}
$Hash.GetFoo()
Line |
18 | $Hash.GetFoo()
| ~~~~~~~~~~~~~~
| Method invocation failed because [System.Collections.Hashtable] does not contain a method named 'GetFoo'
但是,如果您显式转换为以下类型,则此操作有效:
Import-Module "$PSScriptRoot/CustomHashtable.dll"
$Hash = [CustomHashtable]::New(@{
Foo = 'Bar';
})
[CustomHashtable]$Hash += @{
Name = 'Toot';
}
$Hash.GetFoo() //bar
由于许多原因,这并不理想。
PowerShell 是否有任何方法可以拾取此类型化重载?
答:
PowerShell 是否有任何方法可以拾取此类型化重载?
不,至少对于字典类型不是。
TL的;DR:PowerShell 的二进制操作绑定器(计算如何计算二进制运算符表达式的运行时组件,包括 )具有一些硬编码逻辑,专门用于将两个对象添加在一起,这两个对象都实现接口 - 并且它永远不会返回自定义字典类型。@{} + @{}
IDictionary
让我们深入了解源代码,看看它是什么样子的!
PowerShell 的分析器会将表达式转换为 BinaryExpressionAst
对象。然后,PowerShell 的内部编译器必须为给定操作数解析适当的运算符实现,并构造一个可以在运行时实际计算的表达式树。@{} + @{}
如果我们专门查看评估二进制表达式的代码路径,我们会发现编译器将操作推迟到 PSBinaryOperationBinder
类:
public object VisitBinaryExpression(...)
{
// ...
switch (binaryExpressionAst.Operator)
{
// ...
case TokenKind.Plus:
if (lhs.Type == typeof(double) && rhs.Type == typeof(double))
{
return Expression.Add(lhs, rhs);
}
binder = PSBinaryOperationBinder.Get(ExpressionType.Add);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
}
// ...
}
好了,越来越近了 - 现在我们知道应用于两个哈希表的实际实现已在某处解析,我们需要查找受表达式类型约束的代码。+
PSBinaryOperationsBinder
Add
再次深入研究源代码,我们将在活页夹中找到以下路径:
private DynamicMetaObject BinaryAdd(...)
{
// ...
if (target.Value is IDictionary)
{
if (arg.Value is IDictionary)
{
return new DynamicMetaObject(
Expression.Call(CachedReflectionInfo.HashtableOps_Add,
target.Expression.Cast(typeof(IDictionary)),
arg.Expression.Cast(typeof(IDictionary))),
target.CombineRestrictions(arg));
}
return target.ThrowRuntimeError(new DynamicMetaObject[] { arg }, BindingRestrictions.Empty,
"AddHashTableToNonHashTable", ParserStrings.AddHashTableToNonHashTable);
}
// ...
}
所以很明显,当(左侧操作数)和(右侧操作数)都实现接口时,会发生一些特殊的事情——就像这里的情况一样!target
arg
IDictionary
传递给 as 的动态表达式是对此方法 HashtableOps.Add(...)
的引用,其中问题变得明显:Expression.Call
CachedReflectionInfo.HashtableOps_Add
internal static object Add(IDictionary lvalDict, IDictionary rvalDict)
{
IDictionary newDictionary;
if (lvalDict is OrderedDictionary)
{
// If the left is ordered, assume they want orderedness preserved.
newDictionary = new OrderedDictionary(StringComparer.CurrentCultureIgnoreCase);
}
else
{
newDictionary = new Hashtable(StringComparer.CurrentCultureIgnoreCase);
}
// Add key and values from left hand side...
foreach (object key in lvalDict.Keys)
{
newDictionary.Add(key, lvalDict[key]);
}
// and the right-hand side
foreach (object key in rvalDict.Keys)
{
newDictionary.Add(key, rvalDict[key]);
}
return newDictionary;
}
正如你所看到的,要么是有序字典(相当于)要么是哈希表(),因此你观察到的行为。newDictionary
[ordered]@{}
@{}
评论
Append(Hashtable other)
+ @{}
评论
$AnIDictionary + $AnotherIDictionary
+
IDictionary
z