问题
So I write below quick code that should be working for refernce and struct types, but first one does not do anything useful at all and second is useless for List<int>
. Can anyone point out bugs and design issues in below code?
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;
}
}
Why I'm trying to do so: I've read some SO questions about why there is no Swap in C#. Common claim is Swap is not useful and you should just use temp obj to do it. However, I am still not convinced. I am writing my code now and it uses a lot of swap. It looks really uncomfortable to have same code snippet spreaded everywhere. It is still annoying to have to have same lines in more than one method but much better for my projects.
UPDATE:
Based on all input, I realise how little I know about C# and have produced a correct and concise solution:
public static void Swap<T>(ref T o1, ref T o2)
{
var temp = o1;
o1 = o2;
o2 = temp;
}
Two thoughts:
Is it useful to check if o1 and o2 are reference-equal in terms of performance? I guess it is not a very bad idea, but then should I use object.ReferenceEquals()? If yes, and if T is primitive type, does it invoke boxing?
Because ref keyword is used, we can't pass in elements of List. I write an extension method to swap List elements:
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:
The first version, with reference types (the constraint where T : class
), is clearly missing ref
modifiers on the two parameters. Assigning new references to o1
and o2
changes absolutely nothing for the caller when the parameters are not passed "by ref".
So there is really no reason to have separate methods (by the way, two overloads are not allowed to have the same signatures, illegal C#!) for reference types and value types in this case.
Also, it is unclear why you do not wish to swap just because CompareTo
gives 0
. It can still matter to swap the references; it could still be distinct instances.
You are probably confusing two unrelated concepts: (1) Reference types (class
) and value types (struct
). (2) ByRef parameters (ref
or out
keyword) and value parameters (neither ref
nor out
present).
EDIT: You could test your understanding with this example:
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, };
}
}
The "value" of a class is a "reference" to a location of that instance of the class (object).
The "value" of a struct is all the struct instance fields taken togethed.
When something is passed "ByVal", the value is copied and handed to the method. For a class, that only involves copying the reference. We now have two distinct references to the same object on the heap. For a struct, copying the value means copying all the instance fields that this struct has.
When something is passed "ByRef", the method gets the very same value as the caller has. Nothing is copied. Same place in memory. For classes, there is one reference to the object. For structs, there is one place in memory where all the instance fields are kept.
(In the above, "class" could be a class
type, interface
type, delegate
type or array type, and "struct" could be a struct
type or enum
type.)
C# also has pointers, in unsafe
context, for example MyStruct*
is the pointer type corresponding to MyStruct
. Pointers (unlike "references" for class
types) allow "arithmetics" like incrementing a pointer, or adding a value to a pointer. And you can cast pointers, for example from MyStruct*
to byte*
.
But pointers are rarely used. Avoid pointers like this: Use arrays (MyStruct[]
) or generic collection types (e.g. List<MyStruct>
) instead. If you want to re-interprete the data of say a MyStruct
value into a list of four byte
values (why?), make a method that does just this.
来源:https://stackoverflow.com/questions/21143521/my-implementation-of-swapt-does-not-work