具有重载运算符的 Hashtable 子类

Hashtable Subclass With Overloaded Operators

提问人:soulshined 提问时间:11/9/2023 最后编辑:soulshined 更新时间:11/10/2023 访问量:77

问:

以 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 哈希表

评论

1赞 Mathias R. Jessen 11/9/2023
我需要做一些挖掘,但老实说,我认为这是不可行的。PowerShell 的运行时绑定器具有用于二进制操作的特殊硬编码逻辑,该逻辑会导致 PowerShell 将条目从两个操作数复制到新的哈希表并输出该逻辑。换句话说,应用于两个实例将始终产生(内置)哈希表$AnIDictionary + $AnotherIDictionary+IDictionary
0赞 soulshined 11/10/2023
@MathiasR.Jessen,我相信你是对的。自从该方法失败以来,我还没有走到这一步,但是只是在 PowerShell 中打印出结果,重载方法似乎根本没有被调用,因为我没有看到带有后缀的值z
0赞 Mathias R. Jessen 11/10/2023
找到了相关代码,我在下面发布了更全面的解释:)

答:

3赞 Mathias R. Jessen 11/10/2023 #1

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);
    }

    // ...
}

好了,越来越近了 - 现在我们知道应用于两个哈希表的实际实现已在某处解析,我们需要查找受表达式类型约束的代码。+PSBinaryOperationsBinderAdd

再次深入研究源代码,我们将在活页夹中找到以下路径

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);
    }

    // ...
}

所以很明显,当(左侧操作数)和(右侧操作数)都实现接口时,会发生一些特殊的事情——就像这里的情况一样targetargIDictionary

传递给 as 的动态表达式是对此方法 HashtableOps.Add(...)引用,其中问题变得明显:Expression.CallCachedReflectionInfo.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]@{}@{}

评论

1赞 Santiago Squarzon 11/10/2023
很棒的答案!
0赞 soulshined 11/10/2023
同意极好的答案。我将扫描 github,看看是否有任何类似满足此要求的请求,如果没有,我将创建一个。也感谢您的资源链接!非常感谢
0赞 Mathias R. Jessen 11/10/2023
@soulshined 不客气,请这样做!:)FWIW,我个人更喜欢另一种选择是定义一种摄取现有哈希表的方法 - 但这显然不能防止意外:)Append(Hashtable other)+ @{}
0赞 Abraham Zinala 11/11/2023
教我你的方法!很好的答案!O7型