C# void函数传入集合参数排序后没效果为啥

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C# void函数传入集合参数排序后没效果为啥相关的知识,希望对你有一定的参考价值。

代码如图:
private void button2_Click(object sender, EventArgs e)
List<User> datas = new List<User>()
new User ID=2, Name="b",
new User ID=1,Name="a",
new User ID=3,Name="c"
;
doTest(datas);
string str = string.Empty;
foreach (var item in datas)
str += item.ID + "=" + item.Name + ",";

MessageBox.Show(str);

public void doTest(List<User> datas)
datas = datas.OrderBy(d => d.ID).ToList();
datas.Find(d => d.ID == 1).Name = "FK";

doTest 函数在不使用retrun集合,和ref ,out参数类型的情况下,直接定有一个void函数,参数就是User对象集合
最后输出的messagebox结果:
问题1:ID=1的Name有被赋值为"FK",但是排序却没有效果,为什么?
问题2:从ID=1的有被赋值成功,那么作为参数传进来对象集合和在函数中被操作的集合是指向同一块内存的,那为什么排序重新赋值不能成功,而属性的赋值却可以成功呢?
点击button,得到结果截图:

补充:
知道加参数加ref或者out,或者return回去,都可以得到我想要的结果
我想知道的是函数doTest 里的参数datas,它和数据源datas引用变量是指向堆里的同一个内存,我把ID=1的Name赋值为FK是成功的,可以很明显的看出来。
那问题来了,为什么我FK赋值成功了,而排序操作却没成功呢(而且排序操作,是在FK赋值操作的前面)?
我想知道的是内部机制是什么样的,而不是要你告诉我如何可以达到我想要的结果。
感觉现在看起来有点晕,尽然指向同一块堆内存,我FK赋值可以成功,为什么排序却不能成功呢?

先来说下C#中的数据类型.分值类型和引用类型两大类.
值类型:直接存储数据的值,保存在内存中的stack(堆栈)中
引用类型:存储对值的引用,实际上存储的就是一个内存的地址.引用类型的保存分成两块,实际值保存在托管堆(heap)中.实际值的内存地址保存在stack中

当使用引用类型时先找到stack中的地址,再找到heap中的实际值.
也就是说保存引用类型时要用到stack和heap,但使用引用类型时实际上只用到stack中的值,然后通过这个值间接的访问heap中的值
C#预定义的简单类型,像int,float,bool,char都是值类型,另外enum(枚举),struct(结构)也是值类型
string,数组,自定义的class就都是引用类型了.其中的string是比较特殊的引用类型.C#给它增加个字符恒定的特性.
C#函数的参数如果不加ref,out这样的修饰符显式申明参数是通过引用传递外,默认都是值传递.

这里要注意的一个问题是,参数的类型是值类型还是引用类型和传参数时用值传递还是引用传递是两个不同的概念.
假如有void FunTest(int [] array) 和void FunTest(int a) 这两个函数.参数array是引用类型,a是值类型.但是他们传递时都是按值传递.

举个例子说明下
按值传递参数:
class Program

public static void ChangeInt(int num)

num = 123;

public static void ChangeArray(int[] array)

array[0] = 10;
array = new int[] 6, 7, 8, 9 ;

static void Main(string[] args)

int anum = 1;
int[] aarray = 1, 2, 3 ;

ChangeInt(anum);
ChangeArray(aarray);

Console.WriteLine("value of num: " + anum);
Console.Write("value of aarray: ");
foreach (int i in aarray)
Console.Write(i + " ");


结果是:value of anum : 1
value of aarray :10 2 3

可能看到结果会有点奇怪.一般认为值传递就是把值拷贝一份,然后不管在函数中对传入的参数做啥改变,参数之前的值不会受啥影响,所以anum没有变成123,仍然是1

前面有说到引用类型在内存中是保存为两个部分,一个是stack中的内存地址,另一个是heap中的实际值.用时只直接用stack中的值,我们假如stack中的值为0xabcdefgh ,就说是aaraay指向它吧. 那么按值传递时就是把这个stack的值拷贝成另一份就假如是array指向它吧.跟拷贝anum的值1一样.

但是操作内存地址这样的值时不会像整数一样直接操作它,而只会通过它去找heap中的实际值.
于是array[0] = 10.改变了实际上还是heap中数组的值了. 但array = new int [] 6,7,8,9没有对之前传的aarray产生影响.这个操作的意义是在heap中重新开辟一块内存,保存着值6,7,8,9. 这这块内存的地址赋给array,于是它之前的值0xabcdefgh被改写了.但aarray指的值stack值仍没变,仍是0xabcdefgh

按引用传递参数
可以用out或ref显式指定.大部分时候可以通用,只是有一点细小区别.先用ref 来举例吧,还用上面的例子,只是加个了关键字ref
class Program

public static void ChangeInt(ref int num)

num = 123;

public static void ChangeArray(ref int[] array)

array[0] = 10;
array = new int[] 6, 7, 8, 9 ;

static void Main(string[] args)

int anum = 1;
int[] aarray = 1, 2, 3 ;

ChangeInt(ref anum);
ChangeArray(ref aarray);

Console.WriteLine("value of num: " + anum);
Console.Write("value of aarray: ");
foreach (int i in aarray)
Console.Write(i + " ");


结果是:value of anum : 123
value of aarray :6 7 8 9
跟按值传递的结果完全不同吧
num = 123是容易理解.再来说下aarray的值为啥变了吧,按引用传递时aarray指向的stack中的值不会复制一份,而是直接传过去.这样array[0]= 10这样赋值时也同样改变了heap中 1 2 3 的值,变为10 2 3,如果没有array = new int [] 6,7,8,9 这个语句,则它的结果跟上面按值传递是完全一样的.但有个这句话后就不一样,知道上面说了它的含义,在heap中开辟一块新内存值是6 7 8 9,而aarray指向的stack的值被改写了,改为指向保存6 7 8 9的内存地址了.那含有10 2 3的那一块内存其实还继续存在,只是没有谁引用到它了.到时垃圾回收器会把它回收的.

补充:
说下out 和ref的细小区别
ref 传进来的参数必须要先赋值.
像上面 的例子中如果这样写
int num;
ChangeInt(ref int num);
就会出错,必须先给num给个值1.
而且out传进来的参数可以不先赋值.
out num;
ChangeInt(out int num);是对的
另外还有个区别就是如果用out的时候ChangeInt函数中必须有某个地方给num赋值了,而用ref不一定需要在函数中给num赋值
其实这样做的目的很好理解.C#为了确保在任何情况下num必须有个值,不能为空.因为用ref,在调用函数前必须保证参数有值,所以在函数中就不必要求它一定再赋值,而用out由于在调用函数前不用保证参数必须有值,所以在函数中必须保证给它个值

ChangeInt(ref int num)和ChangeInt(out int num)虽然不一样,但是不同共存,不能当作两个不同的函数
而ChangeInt(int num)和上面 的两个函数是完全不一样的,可以放到一起共存,这样的话调用的时候ref ,out这样的关键字不能省的.必须匹配
参考技术A

对集合内部进行排序,如果没有显式指定ref或out,那么在.NET底层它是间接调用了Array类里的一个静态方法Copy() ,对源数据进行一份拷贝然后对拷贝的副本的数据进行排序。当然你可以不加ref或out,而直接对排序数据return回去也可以。

下面是.NET底层Copy方法的几个重载版本的片段:

public static void Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) 
        
            Copy(sourceArray, sourceIndex, destinationArray, destinationIndex, length, false);
        
        
        // 下面这个版本最终调用了C\\C++写的方法
        internal static extern void Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable);

 另外,.NET底层对List类的Sort方法的排序就是调用Array类的Sort排序,下面是.NET底层源码片段:

// List类源码:
public void Sort(Comparison<T> comparison) 
            if( comparison == null) 
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
            
            Contract.EndContractBlock();

            if( _size > 0) 
                IComparer<T> comparer = new Array.FunctorComparer<T>(comparison);
                Array.Sort(_items, 0, _size, comparer);
            
        

本回答被提问者采纳
参考技术B 你在搞笑么?

public void doTest(ref List<User> datas)追问

你不明白我问问题的意思,或者是我没描述好。
我当然知道加参数加ref或者out,或者return回去,都可以
我想知道的是内部机制是什么样的,而不是要你告诉我如何可以达到我想要的结果

追答

你们在搞笑么?

神马的内部排序机制。orderby 是linq 的东西,返回值是新创建的临时IQueryAble序列,你用tolist方法创建了一个新的list,赋值给了形参。class 类在栈上保留引用,内容在托管堆上,你通过复制的引用去修改其内容当然可以改掉。

你采纳的答案也够搞笑的。说什么list的sort,这里有调用list的sort么?真正调用了就会把外面的list一起改掉。

using System;
using System.Collections.Generic;
class User
 public int IDget;set;
 public string Nameget;set;


class Program

 public static void Main(string[] args)
 
  button2_Click(null,null);
 
 privte static void button2_Click(object sender, EventArgs e) 
  List<User> datas = new List<User>() 
   new User ID=2, Name="b",
   new User ID=1,Name="a",
   new User ID=3,Name="c"
  ;
  doTest(datas);
  string str = string.Empty;
  foreach (var item in datas) 
   str += item.ID + "=" + item.Name + ",";
  
  Console.WriteLine(str);
 
 public static void doTest(List<User> datas) 
  datas.Sort((User l, User r)=>return l.ID - r.ID;);
  datas.Find(d => d.ID == 1).Name = "FK";
 

/******************
 * output: 1=FK,2=b,3=c,
 * 
 * ***************/

参考技术C 试试引用传参数

C#线程调用方法时,怎么传参数过去

写一个类,把参数封装一下即可。见以下代码

using System;
using System.Threading;

public class MyThreadClass

    private int x;
    public MyThreadClass (int x)
    
        this.x = x;
    

    public void MyThread()
    
        Console.WriteLine("Parameter = 0", x);
    


class Program

    static void Main(string[] args)
    
        MyThreadClass mtc = new MyThreadClass(100);
        Thread t = new Thread(mtc.MyThread);
        t.IsBackground = true;
        t.Start();

        Console.ReadKey();
    
参考技术A 第1个参数ThreadStart也可以是ParameterizedThreadStart,之后在用thread.start(object
para),注意参数类型是object的,因此你可以自己写1个类,用来存你要用的参数。

以上是关于C# void函数传入集合参数排序后没效果为啥的主要内容,如果未能解决你的问题,请参考以下文章

C#线程调用方法时,怎么传参数过去

为啥在集合中找不到 Mysql 存储过程参数?

C# 用params定义一维参数数组

多线程编程:线程调用函数时传入参数

C语言线程函数参数问题

MyBatis传入多个参数 ,List集合