我的Swap <t>实现不起作用?</t>

时间:2014-01-15 16:55:40

标签: c# generics

所以我在下面写下应该用于引用和结构类型的快速代码,但是第一个代码根本没有做任何有用的事情,其次对于List<int>没用。任何人都可以在下面的代码中指出错误和设计问题吗?

public static void Swap<T>(T o1, T o2) where T : class, IComparable<T>
{
    if (o1.CompareTo(o2) != 0)
    {
        var temp = o1;
        o1 = o2;
        o2 = temp;
    }
}

public static void Swap<T>(ref T o1, ref T o2) where T : struct, IComparable<T>
{
    if (o1.CompareTo(o2) != 0)
    {
        var temp = o1;
        o1 = o2;
        o2 = temp;
    }
}

为什么我要这样做:我已经阅读了一些关于为什么C#中没有交换的问题。常见的声明是Swap没用,你应该只使用temp obj来做。但是,我仍然不相信。我现在正在编写代码,它使用了大量的交换。将相同的代码片段传播到各处看起来真的很不舒服。在多个方法中使用相同的行仍然很烦人,但对我的项目来说要好得多。

更新

基于所有输入,我意识到我对C#知之甚少并且已经产生了正确而简洁的解决方案:

public static void Swap<T>(ref T o1, ref T o2)
{
        var temp = o1;
        o1 = o2;
        o2 = temp;
}

两个想法:

  1. 检查o1和o2在性能方面的参考相等是否有用?我想这不是一个非常糟糕的主意,但是我应该使用object.ReferenceEquals()吗?如果是,如果T是原始类型,它是否会调用拳击?

  2. 因为使用了ref关键字,所以我们无法传递List的元素。我写了一个扩展方法来交换List元素:

    public static void SwapElements<T>(this IList<T> list, int a, int b)
    {
        if (a < 0 || b < 0 || a >= list.Count || b >= list.Count || a > b)
            throw new ArgumentOutOfRangeException();
    
        if (a == b) 
            return;
    
        var t = list[a]; list[a] = list[b]; list[b] = t;
    
        return list;
    }
    

1 个答案:

答案 0 :(得分:0)

第一个版本,带有引用类型(约束where T : class),显然缺少两个参数的ref修饰符。当参数未通过“ref”传递时,为o1o2分配新引用对调用者完全没有任何改变。

因此,在这种情况下,对于引用类型和值类型,确实没有理由使用单独的方法(顺便说一下,两个重载不允许具有相同的签名,非法C#!)。

另外,目前还不清楚为什么你不想仅仅因为CompareTo给出0而进行交换。交换引用仍然很重要;它可能仍然是不同的实例。

您可能会混淆两个不相关的概念:(1)引用类型(class)和值类型(struct)。 (2)ByRef参数(refout关键字)和值参数(既不存在ref也不存在out)。


编辑: 您可以通过此示例测试您的理解:

class MyClass
{
  internal int Field;
}
struct MyStruct
{
  internal int Field; // NB! Many people consider mutable structs evil
}
static class Test
{
  static void Main()
  {
    var a = new MyClass();
    ChangeFieldOfMyClass_ByVal(a);
    Console.WriteLine(a.Field);

    var b = new MyStruct();
    ChangeFieldOfMyStruct_ByVal(b);
    Console.WriteLine(b.Field);

    var c = new MyClass();
    ChangeFieldOfMyClass_ByRef(ref c);
    Console.WriteLine(c.Field);

    var d = new MyStruct();
    ChangeFieldOfMyStruct_ByRef(ref d);
    Console.WriteLine(d.Field);


    var e = new MyClass();
    AssignToParameterMyClass_ByVal(e);
    Console.WriteLine(e.Field);

    var f = new MyStruct();
    AssignToParameterMyStruct_ByVal(f);
    Console.WriteLine(f.Field);

    var g = new MyClass();
    AssignToParameterMyClass_ByRef(ref g);
    Console.WriteLine(g.Field);

    var h = new MyStruct();
    AssignToParameterMyStruct_ByRef(ref h);
    Console.WriteLine(h.Field);
  }

  static void ChangeFieldOfMyClass_ByVal(MyClass p)
  {
    p.Field = 42;
  }
  static void ChangeFieldOfMyStruct_ByVal(MyStruct p)
  {
    p.Field = 42;
  }
  static void ChangeFieldOfMyClass_ByRef(ref MyClass p)
  {
    p.Field = 42;
  }
  static void ChangeFieldOfMyStruct_ByRef(ref MyStruct p)
  {
    p.Field = 42;
  }

  static void AssignToParameterMyClass_ByVal(MyClass p)
  {
    p = new MyClass { Field = 42, };
  }
  static void AssignToParameterMyStruct_ByVal(MyStruct p)
  {
    p = new MyStruct { Field = 42, };
  }
  static void AssignToParameterMyClass_ByRef(ref MyClass p)
  {
    p = new MyClass { Field = 42, };
  }
  static void AssignToParameterMyStruct_ByRef(ref MyStruct p)
  {
    p = new MyStruct { Field = 42, };
  }
}

类的“值”是对该类(对象)实例的位置的“引用”。

结构的“值”是所有采用的结构实例字段。

当某些内容传递“ByVal”时,复制并传递给该方法。对于一个类,只涉及复制引用。我们现在对堆上的同一个对象有两个不同的引用。对于结构,复制值意味着复制此结构具有的所有实例字段。

当某些内容传递“ByRef”时,该方法获得与调用者具有的值相同的值。什么都没有复制。记忆中的同一个地方。对于类,对该对象有一个引用。对于结构体,内存中有一个位置,其中保留了所有实例字段。

(在上文中,“class”可以是class类型,interface类型,delegate类型或数组类型,“struct”可以是struct键入或enum类型。)

C#在<{1}}上下文中也有指针,例如unsafe是与MyStruct*对应的指针类型。指针(与MyStruct类型的“引用”不同)允许“算术”,例如递增指针或向指针添加值。您可以投射指针,例如从classMyStruct*

但很少使用指针。避免这样的指针:使用数组(byte*)或泛型集合类型(例如MyStruct[])。如果您想将List<MyStruct>值的数据重新解释为四个MyStruct值列表(为什么?),请创建一个方法来实现此目的。