zoukankan      html  css  js  c++  java
  • 探究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));
            }
    
           }
    View Code

    在这个类里面我们重写了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
    View Code
  • 相关阅读:
    安装与配置 Elasticsearch
    推荐几个 WebSocket 服务端实现
    GitLab 修改主机名,更换 IP 配置,配置 SMTP
    Choose GitLab for your next open source project
    大数据全栈式开发语言 – Python
    IPC's epoch 6 is less than the last promised epoch 7
    将/home空间从新挂载到/var/lib/docker
    Initialization failed for Block pool <registering> (Datanode Uuid unassigned) service to IP1:8020 Invalid volume failure config value: 1
    查看端口被那个进程占用
    查看java进程启动的详细参数和过程
  • 原文地址:https://www.cnblogs.com/MrZhangLoveLearning/p/4303335.html
Copyright © 2011-2022 走看看