【问题标题】:Passing parameters by reference in CreateInstance在 CreateInstance 中通过引用传递参数
【发布时间】:2017-10-04 21:25:24
【问题描述】:

[编辑:我在这个问题中添加了很多细节,以便更清楚为什么我需要通过引用传递枚举器]

我正在编写一些代码来解析由命令和参数组成的列表。并非所有命令都具有相同数量的参数。例如,列表可以是 -

command1,
100,
command2,
54,
42,
71,
command3,
10,
31,
command1,
82,
command3,
102,
87

(请注意,某些命令可能具有非整数参数)

我正在使用 List 枚举器遍历数据列表。每个命令都有自己的类,能够从列表中解析命令的参数(并执行一系列与该命令相关的其他活动,这些活动在这个精简示例中不需要)。

我有一本字典,可以将命令连接到它们的类 -

var map = new Dictionary<string, Type>
{
    { "command1", typeof(Command1Class) },
    { "command2", typeof(Command2Class) },
    { "command3", typeof(Command3Class) },
};

所以我的基本解析循环如下-

var enumerator = data.GetEnumerator();
while (enumerator.MoveNext())
{
    var command = (string)enumerator.Current;
    codeBlock.AddBlock((Block)Activator.CreateInstance(map[command], new object[] { enumerator }));
}

(所有命令类都派生自相同的基类型,Block)

因此,在每个命令类的构造函数中,它可以解析该命令需要多少个参数,并且在返回主循环时,枚举器将继续遍历这些参数。

例如-

class Command1Class : Block
{
    string param;

    public Command1Class(ref List<object>.Enumerator enumerator)
    {
        enumerator.MoveNext();
        param = (string)enumerator.Current;
    }
}

但我发现枚举器仅在构造函数中本地修改。因此,在从构造函数返回时,枚举器仍然指向命令,而不是继续遍历该命令需要多少个参数。

如果我使用以下样式以非动态方式执行此操作,它会按预期工作,枚举器从构造函数返回时指向下一个命令 -

new SomeClass(ref enumerator)

所以我想知道为什么我的 CreateInstance 代码没有按预期工作,我如何能够动态地做到这一点?

【问题讨论】:

  • 您到底希望发生什么?
  • 不清楚“预期”是什么。
  • 你们说的都对。我添加了更多信息,以使这样做的原因和预期结果更加清晰
  • Activator.CreateInstance 无法将 refout 参数传递给构造函数,以使更改传播到外部。 动态地执行此操作的唯一方法是生成 IL 来执行此操作。但是,由于您也不能像这样动态调用此 IL,因此您需要对要构造的类型的构造函数参数进行标准化,以便可以将其包装在方法中。
  • @LasseV.Karlsen 我认为“Activator.CreateInstance 无法将 ref 或 out 参数传递给构造函数以使更改传播到外部”并不完全正确。您将所有参数作为object[] 数组传递,如果尊重的参数通过ref 或out 传递,则此数组中的元素 更改。因此,变化将在外部可见,而不是以人们可能期望的方式。

标签: c# ref createinstance


【解决方案1】:

你的方法有很多问题,因为Enumerator 是结构(所以,值类型)。这意味着(除其他外)它在许多情况下都会被复制,例如,如果您删除 ref 关键字:

public Command1Class(List<object>.Enumerator enumerator)

然后传递您的枚举器 - 它将被复制并且 Command1Class 构造函数中的枚举器与外部的实例不同。您注意到了这一点并在构造函数中通过引用传递了枚举器,但是当您这样做时,同样的问题再次困扰着您:

(Block)Activator.CreateInstance(map[command], new object[] { enumerator })

您在object[] 数组中传递枚举器(正如CreateInstance 所期望的那样),这又会生成一个副本。这样更清楚:

var args = new object[] { enumerator };
Activator.CreateInstance(typeof(Command1Class), args);

当您创建 args 数组时 - 它已经包含 enumerator 的副本,而不是同一个实例。现在这个副本通过引用传递给您的构造函数,并且确实是高级的,您可以像这样验证:

var args = new [] { enumerator };
Activator.CreateInstance(typeof(Command1Class), args);
// assign copy back to original value
enumerator = (List<object>.Enumerator) args[0];  
// now enumerator is advanced as expected

如果enumerator 是引用类型,实际上也会发生同样的情况,因为当您将其放入数组中以将参数传递给Activator.CreateInstance - 它不再是同一个变量(它指向内存中的同一个对象,而是指针本身不同)。

所以你的方法非常脆弱,可能你应该重新设计它。如果您想坚持下去 - 不要将任何内容传递给构造函数,而是在 Block 类中创建方法:

public void Init(ref List<object>.Enumerator enumerator)

然后不加思考地调用它。

【讨论】:

  • 很好的解释——非常感谢。使用单独的 Init 函数似乎是一个不错的解决方案,所以我会继续这样做。再次感谢
【解决方案2】:

不要使用 ref 你需要传递列表而不是 ref enumerator 并在构造函数中调用 getEnumerator。

class SomeClass
{
    public SomeClass(List<object> list)
    {
        var enumerator = list.GetEnumerator();
        enumerator.MoveNext();
    }
}

class Program
{
    static void Main(string[] args)
    {
        var list = new List<object>();
        var someClass = Activator.CreateInstance(typeof(SomeClass), 
            new object[] { list = list });
    }
}

【讨论】:

  • 感谢您的回复。我在原始问题中添加了更多细节,以便更清楚地说明为什么我需要通过引用传递枚举数以及我期望的结果
猜你喜欢
  • 2010-10-07
  • 2021-12-11
  • 2019-02-12
  • 2011-08-21
  • 2017-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多