有几个有点深层次的基础问题很久都没搞懂 请教下大家

Brown_Sugar 2017-09-04 10:57:35
1. 请问声明为const或者static readonly的字符串对比非静态的的字符串除了不能被修改而且多个类的实例共享同一个变量之外 就没有其他性能优势了吗? 我初始学C#的时候 说给一个字符串赋值为 "" 这样一个空字符串是新开了一个内存空间 但如果写的是string.Empty 那么是直接指向那个变量的内存地址,这样的话后者这种方式性能会更好一点。然而我在.net4.6.1版本中测试 ReferenceEqual("", string.Empty)结果为true 我自己写一个 static readonly string A = “CSDN”; 然后写一个局部变量 string b = "CSDN" 结果ReferenceEqual(a, b)也是true 那么const和static readonly到底比非静态字符串在性能上好在哪里呢?

2. const可以修饰局部变量 static readonly却不能 这是为什么呢? 修饰为const的局部变量和修饰为const的成员变量有什么不同吗?

3. 请看下面这段2句代码
ReferenceEqual("a"+"b"+"", "ab")    //返回true

ReferenceEqual("a"+""+"b", "ab") //返回false

ReferenceEqual($"ab{string.Empty}", "ab") //返回false
为什么第一个返回我是true 后面2句返回的是false?

4.有没有办法声明一个类似于数组这种数据结构 但是其中每一项被赋值之后就不能修改的变量 比如我声明一个
static readonly int[] A = new int[] { 1,2,3 };
虽说这个A无法被修改 但是这个数组中每一项的值还是可以用 A[1]=4;这样的方式修改 那我想让其中的任何一项在第一次赋值后都不能再被修改 可以做到吗?
...全文
646 22 打赏 收藏 转发到动态 举报
写回复
用AI写文章
22 条回复
切换为时间正序
请发表友善的回复…
发表回复
闭包客 2017-09-06
  • 打赏
  • 举报
回复
引用 18 楼 Brown_Sugar 的回复:
[quote=引用 17 楼 From_TaiWan 的回复:]arrEle[0]=new element(4);这变成另一个对象了。好吧给你一个非结构或类的

IReadOnlyList<int> list = new List<int>() { 1, 2, 3 };
这个确实满足我的要求了 不过不理解List既然都是继承这个接口的 那为什么List类本身不能做到这一点 而非要用声明为这个接口的变量接收才行呢[/quote] 这种问题在实际的项目中是很好解决的,你写一个类,里面用 List<T> 来存储数据,然后根据需要开放接口就行了。编程语言不提供这样的功能,是因为没必要。这个 IReadOnlyList 的做法也是在类库中解决的,使用继承接口和隐式转换都可以的。方法多种多样。 总的来说,编程语言,编译器,虚拟机,官方类库,提供给你的是基本的功能,好的程序还是靠广大的程序员写出来的。
闭包客 2017-09-06
  • 打赏
  • 举报
回复
引用 16 楼 Brown_Sugar 的回复:
[quote=引用 15 楼 closurer 的回复:] …… 编译器有时候是故意不优化的……
那为什么不优化呢 我无法理解为什么我14L的代码中第一句优化了 第三句却没有 第四句这个C#6.0的语法也没有优化[/quote] 优化代码也是需要时间的,这会使编译变慢。而且优化过的代码,会出现无法 debug 的情况。况且 "a" + string.Empty + "b" 这种代码在实际项目中是不会出现的。
正怒月神 2017-09-06
  • 打赏
  • 举报
回复
引用 21 楼 closurer 的回复:
这种问题在实际的项目中是很好解决的,你写一个类,里面用 List<T> 来存储数据,然后根据需要开放接口就行了。编程语言不提供这样的功能,是因为没必要。这个 IReadOnlyList 的做法也是在类库中解决的,使用继承接口和隐式转换都可以的。方法多种多样。 总的来说,编程语言,编译器,虚拟机,官方类库,提供给你的是基本的功能,好的程序还是靠广大的程序员写出来的。
是的,具体的逻辑操作和业务操作,自己解决。 类库提供基本的操作方法,怎么组合要靠自己。
班门弄武 2017-09-05
  • 打赏
  • 举报
回复
不用const,查看IL代码可以看出编译后就替换成具体值。 例如,dll A 定义了const=1,dll B 引用了const。当哪天将A.const改成=2,而发布时只发布了A,那么就有问题了,因为B中的A.const位置仍然是1。 static readonly则不会有这个问题。
闭包客 2017-09-05
  • 打赏
  • 举报
回复
string.Empty 本身就是这个类的一个槽点。 源码:

        // The Empty constant holds the empty string value. It is initialized by the EE during startup.
        // It is treated as intrinsic by the JIT as so the static constructor would never run.
        // Leaving it uninitialized would confuse debuggers.
        //
        //We need to call the String constructor so that the compiler doesn't mark this as a literal.
        //Marking this as a literal would mean that it doesn't show up as a field which we can access 
        //from native.
        public static readonly String Empty;
这个注释挺有意思。也指出了你需要调用 string 的构造函数才能避开编译器的优化。不过这在实际项目中没用:

new string(new char[] { 'a' });
  • 打赏
  • 举报
回复
.net 对字符串进行了比较多的优化。例如代码
var x = "哈哈";
那么这时候既可能让 x 引用一个独一无二的字符串对象,也可能让 x 与另外一个变量 z 应用相同的字符串对象。 所以一般来说,不要拿 c 语言底层知识直接硬套到各种高级的编程语言。高级语言也许没有 c、汇编语言那么“高”的底层执行效率,但是一定是因为有了更加复杂宏伟的目标和巨大的编程框架所以才会产生比c、汇编高级好几个数量级的语言。
  • 打赏
  • 举报
回复
关于你的最后一个问题,我得从更加务实的角度来分析回答这类问题才比较实用。其实任何人都可能随时提出某些编译器没有的东西作为需求,永无止境。关键是有多大软件设计价值。对于一些表达领域需求非常明确、非常直观的,肯定在c# 这么好的语言编译器设计中早已经体现了。而对于一些纯粹技术性的、含糊的语言特性,它没有涉及过多。其实“过与不及都是样不可取的”,语言可能为了少干扰使用者,而保持简洁实用。
  • 打赏
  • 举报
回复
例如代码
string.ReferenceEquals(("a" + "b" + " ").Substring(0,2), "ab");
这结果就是 false,而不是 true。这是字符串机制的特性。当你写
string x = y;
的时候,字符串变量看似是引用的,然后字符串类型是一种非常特别的引用类型,它的运行时特征趋势类似于值类型,也就是在你接下来执行
y=z;
的时候,这时候并不会让x 改变,而是x仍然引用原来的字符串对象,而 y 引用新的字符串对象。这个结果就好像是值类型变量赋值时总是引用所产生新的副本而并非修改引用。 当刚刚开始使用一个字符串资源时就会显现 string 原本的引用类型的特性,但是当一个 string 进行了赋值运算而必须看上去有兼容传统编程语言(例如vb1~vb6)的那种类似值类型的copy特性时则它会产生新的副本。 因此,看上去同样是值为"ab"的字符串,它既可能与另外一个值为"ab“的字符串有相同引用、有可能有完全不同的引用,这是 string 机制的复杂性决定的。你对一个字符串稍加运算,就发现字符串运算背后的代价非常巨大。所以尽量避免过多使用字符串运算功能。
正怒月神 2017-09-05
  • 打赏
  • 举报
回复
1 empty和""我记得是一样的。
2 const 产生在编译时,也就是说一确定,就不能修改了。
readonly产生在运行时,你还可以在代码中通过构造函数来改变这个值。
3 我的是vs2012,输出结果不一样啊。


4 这个问题就回到了问题2 readonly产生在运行时,你还可以在代码中通过构造函数来改变这个值。
第一次通过构造函数来初始化。
闭包客 2017-09-05
  • 打赏
  • 举报
回复
其实那些博客都是人云亦云,胡说八道的。 static 是运行时初始化的,const 是编译时。性能的差别就这么一点,微乎其微,微到没有用。你可以测试一下。 const 是 C 遗留下来的习惯,你有兴趣可以研究一下 C 语言为什么要 const。这个在 C# 中保留了,Java 则完全淘汰了,也不会有什么问题。 static readonly 就是访问控制而已,和访问器是一样的,和性能没有半毛钱关系。 你那些字符串的引用判断,是编译器优化的结果。编译器会把同样的字符串引用同一个实例。
秋的红果实 2017-09-05
  • 打赏
  • 举报
回复
问题4:因为数组是引用类型,那么A存储的只是int[]的引用(在堆上的地址),你的readonly只是强调了这个引用不可以被修改,而不能保证:这个引用指向的堆里存储的数据,不能被修改。验证见下面代码 要想实现数组元素不被修改,可以这样做,看代码

struct element
{
    public readonly int ele;
    public element(int input)
    {
        ele = input;
    }
}

static readonly int[] A = new int[] { 1, 2, 3 };

private void button1_Click(object sender, EventArgs e)
{
    element[] arrEle = new element[3] { new element(1), new element(2), new element(3) };
    foreach(element e1 in arrEle)
    {
        MessageBox.Show(e1.ele.ToString());
    }

    //arrEle[0].ele = 100; //修改时会出错

    //A = new int[1]; //试图修改数组的引用A时,会出错

}

秋的红果实 2017-09-05
  • 打赏
  • 举报
回复
这种问题,平时真没怎么注意,const在编译时就被替换成字面常量了(如3,“abc"),其实他不是一个变量,是变量的反义词,执行效率肯定高,因为省去为变量分配存储的过程,直接取常量就行 而readonly,说到底,还是一种变量,特殊变量,获取了值后就不能变 好在有vs这样的工具,你写不对位置,或者试图给const再次赋值,就会有红色浪线提示,哈哈,有时偷懒也是必要的,也就不要过分纠结,因为细微的差别很多,我们平时用的又少。但是,我第一段写的,因该是必须要知道的。 问题2:据我所知,const不能用static修饰。static是针对变量的,可以修饰readonly。局部变量和成员变量,是作用域的区别 问题3:我帮你测试了下,都是true

MessageBox.Show(ReferenceEquals("a"+"b"+"", "ab").ToString());
MessageBox.Show(ReferenceEquals("a"+""+"b", "ab").ToString());
MessageBox.Show(ReferenceEquals("ab"+string.Empty,"ab").ToString());

秋的红果实 2017-09-05
  • 打赏
  • 举报
回复
引用 18 楼 Brown_Sugar 的回复:
[quote=引用 17 楼 From_TaiWan 的回复:]arrEle[0]=new element(4);这变成另一个对象了。好吧给你一个非结构或类的

IReadOnlyList<int> list = new List<int>() { 1, 2, 3 };
这个确实满足我的要求了 不过不理解List既然都是继承这个接口的 那为什么List类本身不能做到这一点 而非要用声明为这个接口的变量接收才行呢[/quote]假如我们定制一辆旅行车,车上需要配备旅行所需要的设备,去南极需要的设备equipment1、去沙漠旅行的equipment2、去三亚的equipment3……,如果车辆出厂时,把所有设备都装上,那么估计得需要安装坦克的发动机,至少对我来讲,即使能开得动,也加不起油啊!说笑外,去沙漠旅行,拉着其他装备根本没用。 厂家的做法,没给车辆添加所有可能的设备,而是留下了接口,去哪里旅行,就接上哪些设备就行,岂不是很方便,但是,这些接口是有规定的,不能随便来,符合规定尺寸的才可以接上去;
Brown_Sugar 2017-09-05
  • 打赏
  • 举报
回复
引用 17 楼 From_TaiWan 的回复:
arrEle[0]=new element(4);这变成另一个对象了。好吧给你一个非结构或类的

IReadOnlyList<int> list = new List<int>() { 1, 2, 3 };
这个确实满足我的要求了 不过不理解List既然都是继承这个接口的 那为什么List类本身不能做到这一点 而非要用声明为这个接口的变量接收才行呢
秋的红果实 2017-09-05
  • 打赏
  • 举报
回复
引用 13 楼 Brown_Sugar 的回复:
[quote=引用 5 楼 From_TaiWan 的回复:] 问题4:因为数组是引用类型,那么A存储的只是int[]的引用(在堆上的地址),你的readonly只是强调了这个引用不可以被修改,而不能保证:这个引用指向的堆里存储的数据,不能被修改。验证见下面代码 要想实现数组元素不被修改,可以这样做,看代码

struct element
{
    public readonly int ele;
    public element(int input)
    {
        ele = input;
    }
}

static readonly int[] A = new int[] { 1, 2, 3 };

private void button1_Click(object sender, EventArgs e)
{
    element[] arrEle = new element[3] { new element(1), new element(2), new element(3) };
    foreach(element e1 in arrEle)
    {
        MessageBox.Show(e1.ele.ToString());
    }

    //arrEle[0].ele = 100; //修改时会出错

    //A = new int[1]; //试图修改数组的引用A时,会出错

}

但这么写的话 也只是 arrEle[0].ele不能修改 而arrEle[0]=new element(4);还是可以修改 因为element声明为结构的话 就一定有个公共无参数构造函数 即使你把有参构造函数用访问修饰符控制 别人还是可以调用无参构造来赋值 只不过他没法改成他想改的值而已 我想可能只有类的数组可以考虑用你这种办法[/quote]arrEle[0]=new element(4);这变成另一个对象了。好吧给你一个非结构或类的

IReadOnlyList<int> list = new List<int>() { 1, 2, 3 };
Brown_Sugar 2017-09-05
  • 打赏
  • 举报
回复
引用 15 楼 closurer 的回复:
…… 编译器有时候是故意不优化的……
那为什么不优化呢 我无法理解为什么我14L的代码中第一句优化了 第三句却没有 第四句这个C#6.0的语法也没有优化
闭包客 2017-09-05
  • 打赏
  • 举报
回复
…… 编译器有时候是故意不优化的……
Brown_Sugar 2017-09-05
  • 打赏
  • 举报
回复
引用 7 楼 hanjun0612 的回复:
3 我的是vs2012,输出结果不一样啊。
引用 4 楼 From_TaiWan 的回复:
问题3:我帮你测试了下,都是true

MessageBox.Show(ReferenceEquals("a"+"b"+"", "ab").ToString());
MessageBox.Show(ReferenceEquals("a"+""+"b", "ab").ToString());
MessageBox.Show(ReferenceEquals("ab"+string.Empty,"ab").ToString());

关于我的第三个问题 我发问题之前测的有点马虎 现在我认真重新整理了下 发比较有代表性的4句代码
Console.WriteLine(ReferenceEquals(string.Empty + "a" + "b", "ab"));    // true

Console.WriteLine(ReferenceEquals("a" + "" + "b", "ab")); ;      // true

Console.WriteLine(ReferenceEquals("a" + string.Empty + "b", "ab")); ;      // false

Console.WriteLine(ReferenceEquals($"{string.Empty}ab", "ab")); ;      // false
这里特别说明一下 前2句代码如果你是在调试模式下在即时窗口中运行 结果也都是false 不过到这里我基本上也明白为什么了 编译器优化能力还有待加强
Brown_Sugar 2017-09-05
  • 打赏
  • 举报
回复
引用 5 楼 From_TaiWan 的回复:
问题4:因为数组是引用类型,那么A存储的只是int[]的引用(在堆上的地址),你的readonly只是强调了这个引用不可以被修改,而不能保证:这个引用指向的堆里存储的数据,不能被修改。验证见下面代码 要想实现数组元素不被修改,可以这样做,看代码

struct element
{
    public readonly int ele;
    public element(int input)
    {
        ele = input;
    }
}

static readonly int[] A = new int[] { 1, 2, 3 };

private void button1_Click(object sender, EventArgs e)
{
    element[] arrEle = new element[3] { new element(1), new element(2), new element(3) };
    foreach(element e1 in arrEle)
    {
        MessageBox.Show(e1.ele.ToString());
    }

    //arrEle[0].ele = 100; //修改时会出错

    //A = new int[1]; //试图修改数组的引用A时,会出错

}

但这么写的话 也只是 arrEle[0].ele不能修改 而arrEle[0]=new element(4);还是可以修改 因为element声明为结构的话 就一定有个公共无参数构造函数 即使你把有参构造函数用访问修饰符控制 别人还是可以调用无参构造来赋值 只不过他没法改成他想改的值而已 我想可能只有类的数组可以考虑用你这种办法
Brown_Sugar 2017-09-05
  • 打赏
  • 举报
回复
引用 2 楼 liucqa 的回复:
Const的变量是嵌入在IL代码中,编译时就加载好,不依赖外部dll(这也是为什么不能在构造方法中赋值)。Const在程序集更新时容易产生版本不一致的情况。 Readonly的变量是在运行时加载,需请求加载dll,每次都获取最新的值。Readonly赋值引用类型以后,引用本身不可以改变,但是引用所指向的实例的值是可以改变的。在构造方法中,我们可以多次对Readonly赋值。
其实上面你说的包括链接里讲的东西大部分我都看过 不过第一个问题可以算是解决了吧 但后面3个问题能否在分别回答下
加载更多回复(2)

110,566

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧