探究foreach对于迭代变量的封装性的研究

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

 int[] ii={0,1,2,3};
            foreach(int m in ii){
                m = 3;//错误        “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(3), this.Name.PadLeft(11), this.Code.PadLeft(11), this.Category.PadLeft(7), this.Price.ToString().PadLeft(8), this.ProductDate.ToString("yyyy-M-d").PadLeft(13));
        }

       }

在这个类里面我们重写了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 < 0 || index >= table.Count)
            {
                throw new Exception("索引超过范围!");

            }
            int i = 0;
            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 = -1;
            }
            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 = -1;
            }
            public void Dispose()
            {

            }

        }
    }

     #endregion

时间: 2025-01-12 01:59:12

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

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

C#中foreach不能改变迭代变量的值 然而此种说法只适用与值类型,更改值类型时会改变在栈上的内存分布 引用类型由于是引用地址的变更,不影响内存分布,所以能够在foreach中更改 至于引用类型中的特殊string类型,猜想是因为string类型的变更不是地址的更改,而是会在堆上面重新开辟一块,所以应该也是会影戏到内存分布 原文地址:https://www.cnblogs.com/X-Q-X/p/10402479.html

引用 foreach 迭代变量”在C#7.3

引言 有C#基础的,当问到循环有哪些,会毫不犹豫的说出的for.do while.foreach及while这几种,但是到具体实际开发中,我们遇到一些问题,比如:到底选择哪种?为什么选择这种?哪种好像都可以?,其实在大多数情况下基本上可以通用,但是遇到比如Dictionary <[key] , [value] >只能用foreach遍历,本文带你了解foreach的原理,以及使用场景. foreach原理 在上边博客<IEnumerable和IEnumerator详解>中,我们自定

JAVA- 面向对象的三大特征(封装性、继承性、多态性)

程序的发展经历了两个主要阶段:面向过程.面向对象.面向对象是当前软件开发的主流. 面向过程就是分析出解决所需要的步骤,然后用函数将这些步骤一步一步实现,使用的时候一个一个一次调用. 面向对象是把构成问题的事务分解成各个对象.建立对象的目的不是为了完成一个步骤而是为了描述某个事物在整个解决问题步骤中的行为. 面向对象是为了专注在程序中采用封装.继承.多态等设计方法.面向对象的设计是一种提供符号设计系统的面向对象的实现过程,它用非常接近实际领域术语的方法吧系统构造成"现实世界"的对象. 面

面向对象的封装性和多态性

面向对象之封装性: 1.继承要符合is-a的关系.即"子类is a 父类"——子类是父类.如:卡车是汽车,卡车is a 汽车. 2.父类中如果用Protected修饰符,表示只允许其子类访问,而不允许其他非子类访问: 父类中如果用private修饰符,子类是不能访问的. 3.除了构造方法不能被子类继承外,其他都可以直接被继承.可以用base关键字调用父类的构造函数:语法如下 :base(参数变量名) 使用base还可以调用父类的属性和方法. 注意:子类构造函数中已经定义了这些参数,在b

javascript中提高代码的封装性

我出的面试题中,有一条是问如何避免页面引用JS,出现函数.变量重复.冲突的. 从大的方面讲,应该引入javascript的模块化开发,符合AMD规范之类: 从小的方面说,大概就是限定变量和函数的作用域了,这也涉及到一点大家如雷贯耳的闭包概念. 众所周知,javascript没有类,只有函数.其实它的函数也跟类差不多了,函数里面可以定义函数.而对于.net来说,直到近期出现的C#7,才支持这一点. 所以,我们可以将函数和变量,都定义在一个函数里面,这样即与外部隔绝矣: <html> <he

Java面向对象设计主要有三大特征:封装性、继承性和多态性

一  封装性   1.1  概念:它是将类的一些敏感信息隐藏在类的类部,不让外界直接访问到,但是可以通过getter/setter方法间接访问. 1.2  目的:我们可以并且有可能在方法中,添加自己的限制,保证数据的有效性:从某种程度上说,封装也提高了代码的健壮性.安全性. 1.3  实现步骤:所有的成员变量全部私有化,格式 :private   数据类型  变量名  ,提供访问成员变量的getter/setter方法. 二  继承性   2.1  概念:子类通过一种方式来接受父类所有的公有的,

JAVA学习第十二课(关键字三final:针对extends打破封装性)

final: final 可以修饰类.方法.变量 final 修饰的类不可以被继承 final 修饰的方法不可以被覆盖 final 修饰的变量是一个常量,只能被修饰一次 内部类只能访问被final修饰的局部变量 继承的弊端: 如下代码: class father { void show() { System.out.println("ni hao ");//父类的方法已经调用了底层系统 } } class son extends father { void show() { Syste

验证码确保php无输出、sql语句的封装性、文件上传的工具类【这三个重点工具类实现】

1.php代码在引入中不会进行结束或者确保结束之后没有空格,来保证php在被包含中没有被输出[防止header和session_start()对输出的控制]实质上,需要注意的就是,要不就进行输出缓存控制以及php开始标签前没有空格 验证码这个功能需要header和session两个功能[尤其需要注意输出的问题] [总结:防止php代码中带着一些输出的问题](1)在php标签中开始<?php 前顶格(2)php结束符要不不写,写了就不要在结束之后还有换行[防止该文件被包含之后提前出线输出](3)或

谈谈C++的三大特性之一:封装性 (转载)

引言 对象的C++语言与以往的模块化程序语言的不同点在于:数据与操作数据的函数连接起来(即:封装性),结构紧凑,数据安全.正是由于这种封装性,大大强化了C++语言的可移植性及数据的安全性.类封装的形式很简单,本文主要谈谈封装的内部结构. 实例问题 类的内部数据存储地址仅表示相对对象首地址的地址偏移量.实例(引自:疯狂学习ING<作者网名>)如下: #include <iostream.h> class base { // 假定有很多成员 //..... //..... }; cla