众所周知教科书上对于foreach之中的注释是在遍历过程中无法改变其遍历的元素
例如声明一个数组

 int[] ii={,,,};
foreach(int m in ii){
m = ;//错误 “m”是一个“foreach 迭代变量”,无法为它赋值
Console.WriteLine(m); }

由上面可以知道,我们无法改变数组里面的值,但是foreach语句是为了集合而创建的,数组只是集合的一种,而其他集合会是怎么样的呢?
C#里面为我们创建好了好几个集合类,List<T> ,Array等都是集合,现在我们就使用List<T>作为集合来验证我们的想法,现在我们来创建一个类型声明为Product类。

 public class Product
{
public int Id { set; get; }
public string Name { set; get; }
public string Code { set; get; }
public String Category { get; set; }
public decimal Price { get; set; }
public DateTime ProductDate { get; set; }
public override string ToString()
{
return String.Format("{0}{1}{2}{3}{4}{5}", this.Id.ToString().PadLeft(), this.Name.PadLeft(), this.Code.PadLeft(), this.Category.PadLeft(), this.Price.ToString().PadLeft(), this.ProductDate.ToString("yyyy-M-d").PadLeft());
} }

在这个类里面我们重写了ToString方法以便我们能更好看到输出结果。
现在我们声明一个实例。

 Product pr = new Product();
pr.Id = 1;
pr.Name = "肥皂";
pr.Price = 1M; pr.ProductDate = DateTime.Parse("2015-02-14");
pr.Code = "0001";
pr.Category = "日用品";

  将pr加入到集合List中

 List<Product> list = new List<Product>();
list.Add(pr);

  然后我们遍历它

 foreach (Product prd in list)
{
Console.WriteLine(prd.ToString());
}

                      输出结果是

Id    商品名  产品代号   种类        价格     生产日期
0 肥皂 0001 日用品 1 2015-2-14

   我们尝试在遍历修改一下迭代变量

foreach (Product prd in list)
{
Console.WriteLine(prd.ToString());
}
foreach (Product prd in list)
{
prd.Id = 2; Console.WriteLine(prd.ToString()); }

  输出结果是

Id    商品名  产品代号   种类        价格     生产日期
0 肥皂 0001 日用品 1 2015-2-14
2 肥皂 0001 日用品 1 2015-2-14

  

修改成功了,成功改变了迭代变量的元素,我们此时将pr.Id输出发现pr的Id也被修改成2了,这是为什么了?
我们修改prd的元素不但没报错,还改变了原始值。难道是因为我们修改的是prd.Id而不是prd所以我们成功了吗?
那好我们再次修改将Product类修改成为一个结构(将public class Product成 public struct Product)。再次运行上面的代码,发现连编译都通过不了。显示““prd”是一个“foreach 迭代变量无法为它赋值””。
现在来通过分析集合类的结构来解释这个问题,集合类如果要使用foreach方法必须包含“GetEnumerator”的公共定义,而该方法的返回值是一个Enmerator<T>,用通俗的话来讲就是一个结合类要调用这个foreach方法C#要求它能得到一个枚举器,这个枚举器是一个类型,他必须要有 bool MoveNext()方法, T Current返回T类型的一个属性, void Reset()方法。我们每次使用foreach方法,都要调用这个枚举器类型的方法,我们使用Current属性返回当前迭代的变量,因为Current属性只有get方法所以只能得到Product的实例里面的值,当我们想给迭代变量赋值时就会报错,因为我们没有set方法。但是为什么我们却可以修改Product类中字段,而不可修改Product结构中的字段呢?这里牵涉到值类型和引用类型在内存中存贮的差异,简单来说,我们在堆上如果存在两个变量,一个是值类型,一个是引用类型,当我们对值类型变量(也就是结构)做出prd.Id时,我们现在的位置还是在堆上这个值类型实例的位置,我们如果只有get方法只能得到其值无法修改,但是如果我们是一个引用类型,当我们对引用类型实例(也就是类)做出prd.Id时,因为引用类型变量存贮的只是一个地址,我们prd.Id的位置会直接转移到实例的字段,而不是这个变量上面,所以我们使用prd.Id并不是得到迭代变量,而是得到迭代变量的实例上面。
仔细想想其实这就是因为一个引用类型A的成员如果包含了值类型成员B和引用类型成员C时,这个A类型的实例,如果要阻止修改A类型实例a,那么a里面值类型B的实例b不能修改,因为b就贮存在a里面,而a里面引用类型C的实例c却可以修改,因为a里面就贮存了实例c的地址,修改实例c里面的内容并不会修改a里面实例c的地址。
下面是product的集合类productCollection的代码,代码出自前辈张子阳的博客。大家感兴趣可以了解一下。

#region product集合类型
public class ProductCollection : IEnumerable<Product>
{
//使用哈希表存贮Product
private Hashtable table; /// <summary>
/// 构造函数可以添加Product类实例
/// </summary>
/// <param name="array">Product实例</param>
public ProductCollection(params Product[] array)
{
table = new Hashtable();
foreach (Product pp in array)
{
this.Add(pp);
}
} /// <summary>
/// 索引器
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public Product this[int index]
{
get
{
string selected = getKey(index);
return table[selected] as Product; }
set
{
string selected = getKey(index);
table[selected] = value; } } public Product this[string key]
{
get
{
String selected = getKey(key);
return table[selected] as Product;
}
set
{
string selected = getKey(key);
table[selected] = value;
} }
private string getKey(int index)
{
if (index < || index >= table.Count)
{
throw new Exception("索引超过范围!"); }
int i = ;
string selected = "";
foreach (string item in table.Keys)
{
if (i == index)
{
selected = item;
break;
}
i++; }
return selected;
} private string getKey(string key)
{
foreach (string k in table.Keys)
{
if (key == k) return k;
}
throw new Exception("不存在该键值");
}
public void Add(Product item)
{
foreach (string key in table.Keys)
{
if (key == item.Code)
{
throw new Exception(item.Code + "产品代码不能重复");
}
}
table.Add(item.Code, item);
}
public int Count
{
get
{
return table.Count;
}
} public void Insert(int index, Product item)
{ }
public void Remove(Product item)
{ }
/// <summary>
/// 返回一个枚举器
/// </summary>
/// <returns>枚举器</returns> public IEnumerator<Product> GetEnumerator()
{
return new ProductEnumerator(this);
}
IEnumerator IEnumerable.GetEnumerator()
{
return new ProductEnumerator(this);
}
public class ProductEnumerator : IEnumerator<Product>
{ public readonly ProductCollection collection;
private int index;
public ProductEnumerator(ProductCollection collection)
{
this.collection = collection; index = -;
}
public Product Current
{
get
{
return collection[index];
} }
object IEnumerator.Current
{
get
{
return collection[index];
} }
public bool MoveNext()
{
index++;
if (index >= collection.Count)
{
return false; }
else return true;
}
public void Reset()
{
index = -;
}
public void Dispose()
{ } }
} #endregion

探究foreach对于迭代变量的封装性的研究的更多相关文章

  1. C# foreach 值类型及引用类型迭代变量改变的方式

    C#中foreach不能改变迭代变量的值 然而此种说法只适用与值类型,更改值类型时会改变在栈上的内存分布 引用类型由于是引用地址的变更,不影响内存分布,所以能够在foreach中更改 至于引用类型中的 ...

  2. [改善Java代码]使用静态内部类提高封装性

    建议38: 使用静态内部类提高封装性 Java中的嵌套类(Nested Class)分为两种:静态内部类(也叫静态嵌套类,Static Nested Class)和内部类(Inner Class).内 ...

  3. C++的封装性

    C++的封装性 C++的阶段,我想根据C++的一些特有的特性分别写一些专题,每个专题我都捎带讲一些语法,当然不会很多,我还是会像C语言那样,内存结构贯穿始终,有汇编就有真相…… 本专题,我们讲述封装性 ...

  4. java封装性、继承性及关键字

    方法的参数传递(重点.难点)1.形参:方法声明时,方法小括号内的参数   实参:调用方法时,实际传入的参数的值 2.规则:java中的参数传递机制:值传递机制 1)形参是基本数据类型的:将实参的值传递 ...

  5. Java面向对象(封装性概论)

     Java面向对象(封装性概论) 知识概要:                   (1)面向对象概念 (2)类与对象的关系 (3)封装 (4)构造函数 (5)this关键字 (6)static关键 ...

  6. [ Java学习基础 ] Java的封装性与访问控制

    Java面向对象的封装性是通过对成员变量和方法进行访问控制实现的,访问控制分为4个等级:私有.默认.保护和公有,具体规则如下表: 1.私有级别 私有级别的关键字是private,私有级别的成员变量和方 ...

  7. Java核心技术第四章——1.封装性

    封装性(有时称为数据隐藏): 实现封装的关键在于绝对不能让类中的方法直接地访问其他类的实例域值.程序仅通过对象的方法与对象的数据进行交互. 给对象赋予了"黑盒"的特征,提高了重用性 ...

  8. JavaScript大杂烩3 - 理解JavaScript对象的封装性

    JavaScript是面向对象的 JavaScript是一种基于对象的语言,你遇到的所有东西,包括字符串,数字,数组,函数等等,都是对象. 面向过程还是面向对象? JavaScript同时兼有的面向过 ...

  9. Java10-java语法基础(九)——java的封装性

    Java10-java语法基础(九)——java的封装性 一.Java的三大特性:封装.多态.继承 封装:通过类封装对象的数据成员和成员方法,保证只有可信的类或者对象能够访问这些方法和数据成员,对不可 ...

随机推荐

  1. js判断浏览器类型以及版本

    你知道世界上有多少种浏览器吗?除了我们熟知的IE, Firefox, Opera, Safari四大浏览器之外,世界上还有近百种浏览器. 几天前,浏览器家族有刚诞生了一位小王子,就是Google推出的 ...

  2. javascript实现倒计时程序

    最近在网上看到一道这样的面试题: 题:  网页中实现一个计算当年还剩多少时间的倒数计时程序,要求网页上实时动态显示“××年还剩××天××时××分××秒”? 我实现了,发现挺有意思,下面把我的代码贴出来 ...

  3. java web实现img读取盘符下的图像

    最近做了一个项目,用户上传图片后通过img控件显示出来.大家都知道img通过src属性就可以显示图片.如<img src="http://127.0.0.1/a/b/abc.jpg&q ...

  4. 方法覆盖(override)”的要点

    方法覆盖要求子类与父类的方法一模一样,否则就是方法重载(overload)!请自行编写代码测试以下特性:在子类中,若要调用父类中被覆盖的方法,可以使用super关键字. 结论:          在“ ...

  5. [总结] Stack: Java V.S. C++

    小结一下Stack 的主要API操作. 在c++ 和 java 中,stack 的操作几乎相同,只有查询栈顶元素一项操作的名称不同 (top() v.s. peek()) . 此外,在构造函数中,Ja ...

  6. 异步编程设计模式Demo - PrimeNumberCalculator

    using System; using System.Collections; using System.Collections.Specialized; using System.Component ...

  7. asp.net 中如何判断字符串中有几个逗号 (asp也通用)

    如: 字符串 a="1,2,3"; 怎样判断a 中的逗号 有几个 len(a)-len(replace(a,",",""))

  8. 在Ubuntu上安装VmTools

    1.添加VmTools 2.解压 .tag.gz文件 使用Linux命令: tar –zxvf src –c dis -c: 建立压缩档案 -x:解压 -t:查看内容 -r:向压缩归档文件末尾追加文件 ...

  9. [TYVJ] P1031 热浪

    热浪 背景 Background USACO OCT09 9TH   描述 Description 德克萨斯纯朴的民眾们这个夏天正在遭受巨大的热浪!!!他们的德克萨斯长角牛吃起来不错,可是他们并不是很 ...

  10. Codeforces 509F Progress Monitoring

    http://codeforces.com/problemset/problem/509/F 题目大意:给出一个遍历树的程序的输出的遍历顺序b序列,问可能的树的形态有多少种. 思路:记忆化搜索 其中我 ...