【问题标题】:Changing size of array in extension method does not work?在扩展方法中更改数组的大小不起作用?
【发布时间】:2015-01-06 13:07:33
【问题描述】:

所以基本上我为数组类型编写了我的小Add 扩展方法。

using System;
using System.Linq;
public static class Extensions 
{
    public static void Add<T>(this T[] _self, T item)
    {
        _self = _self.Concat(new T[] { item }).ToArray();
    }
}
public class Program
{
    public static void Main()
    {
        string[] test = { "Hello" };
        test = test.Concat(new string[] { "cruel" }).ToArray();
        test.Add("but funny");
        Console.WriteLine(String.Join(" ", test) + " world");
    }
}

输出应该是Hello cruel but funny world,但but funny 永远不会在扩展方法中连接。

在扩展中编辑相同的数组似乎也不起作用:

using System;
using System.Linq;
public static class Extensions 
{
    public static void Add<T>(this T[] _self, T item)
    {
        Array.Resize(ref _self, _self.Length + 1);
        _self[_self.Length - 1] = item;
    }
}
public class Program
{
    public static void Main()
    {
        string[] test = { "Hello" };
        test = test.Concat(new string[] { "cruel" }).ToArray();
        test.Add("but funny");
        Console.WriteLine(String.Join(" ", test) + " world");
    }
}

我在这里做错了什么,如何将其用作扩展程序?

.dotNet 小提琴:https://dotnetfiddle.net/9os8nYhttps://dotnetfiddle.net/oLfwRD

(如果能找到一种方法让我可以继续通话test.Add("item");,那就太好了)

【问题讨论】:

  • 不能使用扩展方法来完成,因为 this 和 ref 关键字不能一起使用。您可以将新数组作为结果返回并进一步使用。
  • 您应该真正使用List&lt;&gt;,如果您在某些时候需要数组,只需执行.ToArray()。您正在尝试做 List&lt;&gt; 已经在做的事情。
  • @Franck 我正在使用 Unity3D,编辑器以更好的方式处理数组(实际上它根本不会处理 List),出于性能原因,它们应该保留数组,因此扩展方法只会在编辑器中被调用,而不是游戏本身。
  • @modiX 在这种情况下,您应该将 Unity 标签添加到问题中。 List&lt;&gt; 根据微软源代码实际上使用一个数组来存储数据,所以List&lt;string&gt;string[] 是一回事。不同之处在于少数额外属性的分配大小。访问数据的速度相同,并且添加由 Microsoft 自己优化。

标签: c# arrays unity3d extension-methods concat


【解决方案1】:

您正在为参数分配新的引用,除非您将其作为ref 参数传递,否则它不会更改实际数组。由于这是一种扩展方法,因此它不是一个选项。所以考虑使用普通的方法:

public static void Add<T>(ref T[] _self, T item)
{
    _self = _self.Concat(new T[] { item }).ToArray();
}

Add(ref test, "but funny");

或者如果你坚持使用扩展方法,你需要将数组设置为第二个参数才能使用 ref:

public static void AddTo<T>(this T item, ref T[] arr, )
{
    arr = arr.Concat(new T[] { item }).ToArray();
}

"but funny".AddTo(ref test);

Array.Resize 不起作用。因为它更改了_self,而不是test 数组。现在,当您传递不带 ref 关键字的引用类型时,将复制该引用。是这样的:

string[] arr1 = { "Hello" };
string[] arr2 = arr1;

现在,如果您为arr2 分配一个新引用,它不会更改arr1 的引用。Array.Resize 所做的是,由于无法调整数组大小,它会创建一个新数组并复制所有元素到一个新数组,并将该新引用分配给 parameter(在这种情况下为_self)。因此它更改了_self 指向的位置,但由于_selftest是两个不同的引用(例如 arr1arr2),更改其中一个不会影响另一个。

另一方面,如果像我的第一个示例一样将数组作为 ref 传递给方法,Array.Resize 也将按预期工作,因为在这种情况下不会复制引用:

public static void Add<T>(ref T[] _self, T item)
{
    Array.Resize(ref _self, _self.Length + 1);
    _self[_self.Length - 1] = item;
}

【讨论】:

  • 我更新了我的问题,因为使用 Array.Resize 的另一种方式也不起作用。我不明白为什么对_self 的引用会这样。不需要使用 ref 关键字,因为它已经通过引用调用了。很高兴找到一个解决方案,我可以使用扩展方法,如列表上的Add 方法。 IE。 test.Add("item");。有什么想法吗?
  • @modiX 我添加了关于为什么它不起作用的解释。如果有什么不清楚的地方告诉我
  • 谢谢,所以当它更改_self 时,它只会更改Add 扩展方法范围内的变量。这假设 _self 作为值传递,而不是作为引用传递。但这就是重点。 Array默认应该是引用调用,为什么没有放到扩展方法中?
  • @modiX 你应该知道引用类型变量不保存实际值,它们在内存中保存一个地址,可以找到实际对象。一旦你明白你会知道如果你传递了一个引用类型参数,它的引用就会被复制到另一个变量中。就像在我的 arr1 和 arr2 示例中一样。所以,如果我改变 arr2 指向的位置,它不会影响 arr1,因为它们是完全不同的变量。
  • modiX - 假设您不是在编写扩展方法,而是在 Array 类型上编写实际方法。你正在做的是说this = ...。这是无效的:您需要返回新实例,或者自己修改。由于数组是不可变的,你不能自己修改。
【解决方案2】:

我相信_self = 会在为参数_self 创建的局部变量中创建_self 对象的副本,因此不会更新原始对象。使用列表等引用类型,或创建返回新数组的静态方法。

就原因的“感觉”而言:您正在对变量实例调用方法 - 您无法从在其上下文下执行的代码中更改该实例

【讨论】:

  • 对不起,是的,我也不确定我的意思。关键是您正在覆盖局部变量_self,而不是更新调用该方法的源表达式
  • 啊 - 我的意思是不可变的。 Array.Resize 不修改数组,它返回一个新的调整大小的 AFAIK
【解决方案3】:

您可以像这样更改代码:

public static class Extensions 
{
    public static T[] Add<T>(this T[] _self, T item)
    {
        return _self.Concat(new T[] { item }).ToArray();
    }
}
public class Program
{
    public static void Main()
    {
        string[] test = { "Hello" };
        test = test.Concat(new string[] { "cruel" }).ToArray();
        test = test.Add("but funny");
        Console.WriteLine(String.Join(" ", test) + " world");
    }
}

附带说明 - 用法与 Concat 方法相同。

【讨论】:

  • 使用这个不好,因为它违反了 Add() 修改对象本身的框架中类似类的预期行为。有人很容易错误地使用该方法。至少给该方法起一个不同的名称(如 Concat),这样可以清楚地表明对象本身没有被修改。
  • @NineBerry OP 决定这样命名。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多