• 如下代码不能通过编译 1 static void Main(string[] args) 2 { 3 int i = 0; 4 for(int i = 0; i < 10; i++) 5 { 6 WriteLine($"i={i}"); ...【错误原因】Line3与Line4中都用i作为了变量名,...

    在VS2017中,如下代码不能通过编译

    using System;
    using static System.Console;
    
    namespace test
    {
        class Program
        {
    		static void Main(string[] args)
     		{
    			int i = 0;
    			for(int i = 0; i < 10; i++)
    			{
             		WriteLine($"i={i}");
         		}
         		ReadKey();
     		}
     	}
     }
    

    【错误原因】Line3与Line4中都用i作为了变量名,这在C#中是不允许的,虽然他们俩一个在Main()的作用域内,另一个在for循环的作用域内,但是Main()嵌套了for循环,这样的话对于编译器来说两个变量i会产生歧义。

    【解决方案】把其中一个变量名作更改即可
    下面的例子就没有问题(C#6语法)

    using System;
    using static System.Console;
    
    namespace test
    {
        //int i = 0; //错误,命名空间不能直接包含字段或方法之类的成员
        class Program
        {
            int i = 4396; //没问题
            static void Main(string[] args)
            {
                {
                    int i = 0;//没问题,同级的块中字段可以相同
                }
    
                for(int i = 0; i < 10; i++)//没问题,同级的块中字段可以相同
                {
                    WriteLine($"i={i}");
                    
                }
                Program p = new Program();
                WriteLine($"Global i={p.i}");
                ReadKey();
            }
        }
    }
    

    运行结果:

    i=0
    i=1
    i=2
    i=3
    i=4
    i=5
    i=6
    i=7
    i=8
    i=9
    Global i=4396
    

    至于为何Program类的成员变量i没有问题,我的理解是:C#中所有变量都是依托于类的,对于一个类来说,作用范围最大的某过于其public修饰的静态成员变量,其次就是普通成员变量了,因此可以把这里的i看作全局变量(至少对这个例子是的),而上述的错误是对于局部变量而言的。(如果这个解释有问题,欢迎在评论区纠正)

    注:C++中可以在内层作用域中重新定义外层作用域已有的名字
    因此以下C++代码没毛病

    #include<iostream>
    using namespace std;
    int main(){
    	int i=0;
    	for(int i=0;i<10;i++){
    		cout<<"i="<<i<<endl;
    	}
    	return 0;
    }
    

    有关C++变量作用域可以点击下面的链接了解:
    C++作用域

    展开全文
  • CS0136 无法范围中声明名为“channel”的局部变量或参数,因为该名称在封闭局 .ChildHandler(new ActionChannelInitializer<ISocketChannel>(channel_a => { var pipel...

     

     

    CS0136 无法在此范围中声明名为“channel”的局部变量或参数,因为该名称在封闭局

     

     

         .ChildHandler(new ActionChannelInitializer<ISocketChannel>(channel_a =>
                        {
                            var pipeline = channel_a.Pipeline;

    }

     

    展开全文
  • 使用C#开发一个服务端使用的是wpf项目,服务端为手机客户端提供不同的访问URL,根据手机端访问服务器所使用的URL不同做出不同的回应,当然什么URL做出什么回应是由协商好的API决定的。 问题描述: 问题一:如何...
  • C# 7 局部函数剖析

    2019-08-13 08:53:00
    局部函数是C# 7中的一个新功能,允许一个函数中定义另一个函数。 何时使用局部函数? 局部函数的主要功能与匿名方法非常相似:某些情况下,创建一个命名函数读者的认知负担方面代价太大。有时,函数本身就是另...

    局部函数是C# 7中的一个新功能,允许在一个函数中定义另一个函数。

    何时使用局部函数?

    局部函数的主要功能与匿名方法非常相似:在某些情况下,创建一个命名函数在读者的认知负担方面代价太大。有时,函数本身就是另一个函数的部分逻辑,因此用一个单独的命名实体来污染“外部”范围是毫无意义的。

    您可能认为此功能是多余的,因为匿名委托或Lambda表达式可以实现相同的行为。但事实并非如此,匿名函数有一定的限制,其特征可能不适合您的场景。

    用例1:迭代器中的先决条件

    这是一个简单的函数,逐行读取一个文件。您知道什么时候ArgumentNullException会被抛出来吗?

        public static IEnumerable<string> ReadLineByLine(string fileName)
        {
            if (string.IsNullOrEmpty(fileName)) throw new ArgumentNullException(nameof(fileName));
            foreach (var line in File.ReadAllLines(fileName))
            {
                yield return line;
            }
        }
         
        // 什么时候发生错误?
        string fileName = null;
        // 这里?
        var query = ReadLineByLine(fileName).Select(x => $"\t{x}").Where(l => l.Length > 10);
        // 还是这里?
        ProcessQuery(query); 

    包含yield return的方法很特殊。它们叫做 迭代器块(Iterator Blocks),它们很懒。这意味着这些方法的执行是“按需”发生的,只有当方法的客户端调用MoveNext生成迭代器时,才会执行它们中的第一个代码块。在我们的例子中,这意味着错误只会在ProcessQuery方法中发生,因为所有的LINQ操作符都是懒惰的。

    显然,该行为是不可取的,因为该ProcessQuery方法抛出的异常ArgumentNullException将不具有关于该上下文的足够信息。所以最好尽早抛出异常 - 客户端调用ReadLineByLine时,而不是当客户端处理结果时。

    为了解决这个问题,我们需要将验证逻辑提取到一个单独的方法中。匿名函数是最佳候选,但匿名委托和Lambda表达式不支持迭代器块:

    VB.NET中的 Lambda表达式支持迭代器块。

        public static IEnumerable<string> ReadLineByLine(string fileName)
        {
            if (string.IsNullOrEmpty(fileName)) throw new ArgumentNullException(nameof(fileName));
         
            return ReadLineByLineImpl();
         
            IEnumerable<string> ReadLineByLineImpl()
            {
                foreach (var line in File.ReadAllLines(fileName))
                {
                    yield return line;
                }
            }
        }
    

    用例2:异步方法中的先决条件

    异步方法与异常处理有类似的问题,在标记有async关键字的方法中抛出的任何异常,会在一个失败的Task中显现:

        public static async Task<string> GetAllTextAsync(string fileName)
        {
            if (string.IsNullOrEmpty(fileName)) throw new ArgumentNullException(nameof(fileName));
            var result = await File.ReadAllTextAsync(fileName);
            Log($"Read {result.Length} lines from '{fileName}'");
            return result;
        }
         
        
        string fileName = null;
        // 无异常
        var task = GetAllTextAsync(fileName);
        // 以下行将抛出异常
        var lines = await task;

    从技术上说,async是一个上下文关键字,但这并不改变我的观点。

    您可能认为错误发生时没有太大差异,但这远非如此。失败的Task意味着该方法本身未能做到应该做的事情,问题出在方法本身或方法所依赖的某一个构建块中。

    在系统中传递结果Task时,急切的先决条件验证尤为重要。在这种情况下,很难理解什么时候出现什么问题。局部函数可以解决这个问题:

        public static Task<string> GetAllTextAsync(string fileName)
        {
            // 提前参数验证
            if (string.IsNullOrEmpty(fileName)) throw new ArgumentNullException(nameof(fileName));
            return GetAllTextAsync();
         
            async Task<string> GetAllTextAsync()
            {
                var result = await File.ReadAllTextAsync(fileName);
                Log($"Read {result.Length} lines from '{fileName}'");
                return result;
            }
        }

    用例3:迭代器块的局部函数

    不能在Lambda表达式中使用迭代器是一个非常麻烦的问题。这是一个简单的例子:如果要获取类型层次结构中的所有字段(包括私有的),则必须手动遍历继承层次结构。但遍历逻辑是特定方法的,应尽可能保持局部可用:

        public static FieldInfo[] GetAllDeclaredFields(Type type)
        {
            var flags = BindingFlags.Instance | BindingFlags.Public |
                        BindingFlags.NonPublic | BindingFlags.DeclaredOnly;
            return TraverseBaseTypeAndSelf(type)
                .SelectMany(t => t.GetFields(flags))
                .ToArray();
         
            IEnumerable<Type> TraverseBaseTypeAndSelf(Type t)
            {
                while (t != null)
                {
                    yield return t;
                    t = t.BaseType;
                }
            }
        }

    用例4:递归匿名方法

    默认情况下,匿名函数无法引用自身。要解决此限制,您应该声明一个委托类型的局部变量,然后在Lambda表达式或匿名委托中使用该局部变量:

        public static List<Type> BaseTypesAndSelf(Type type)
        {
            Action<List<Type>, Type> addBaseType = null;
            addBaseType = (lst, t) =>
            {
                lst.Add(t);
                if (t.BaseType != null)
                {
                    addBaseType(lst, t.BaseType);
                }
            };
         
            var result = new List<Type>();
            addBaseType(result, type);
            return result;
        }

    这种方法可读性不强,类似的解决方案,局部函数感觉会更自然:

        public static List<Type> BaseTypesAndSelf(Type type)
        {
            return AddBaseType(new List<Type>(), type);
         
            List<Type> AddBaseType(List<Type> lst, Type t)
            {
                lst.Add(t);
                if (t.BaseType != null)
                {
                    AddBaseType(lst, t.BaseType);
                }
                return lst;
            }
        }

    用例5:内存分配

    如果您曾经开发过一个性能要求非常高的的应用程序,应该知道匿名方法的开销也不小:

    • 委托调用的开销(非常小,但确实存在);
    • 如果Lambda捕获本地变量或封闭方法的参数,则需要分配2个堆内存(一个用于闭包实例,另一个用于委托本身);
    • 如果Lambda捕获一个封闭的实例状态,则需要分配1个堆内存(只是分配委托);
    • 只有当Lambda没有捕获任何东西或捕获静态时,分配0个堆内存。

    但是局部函数的分配模式不同。

        public void Foo(int arg)
        {
            PrintTheArg();
            return;
            void PrintTheArg()
            {
                Console.WriteLine(arg);
            }
        }

    如果一个局部函数捕获一个局部变量或一个参数,那么C#编译器会生成一个特殊的闭包结构,实例化它并通过引用传递给一个生成的静态方法:

        internal struct c__DisplayClass0_0
        {
            public int arg;
        }
         
        public void Foo(int arg)
        {
            // Closure instantiation
            var c__DisplayClass0_ = new c__DisplayClass0_0() { arg = arg };
            // Method invocation with a closure passed by ref
            Foo_g__PrintTheArg0_0(ref c__DisplayClass0_);
        }
         
        internal static void Foo_g__PrintTheArg0_0(ref c__DisplayClass0_0 ptr)
        {
            Console.WriteLine(ptr.arg);
        }

    (编译器生成无效字符的名称,例如<>。为了提高可读性,我更改了名称并简化了代码。)

    局部函数可以捕获实例状态、局部变量或参数,不会发生堆内存分配。

    局部函数中使用的局部变量应该在局部函数声明站点中明确指定。

    堆内存分配将发生的情况很少:

    • 局部函数被明确地或隐式地转换为委托。

    如果局部函数捕获静态/实例字段但不捕获局部变量/参数,则只会发生委托分配。

        public void Bar()
        {
            // Just a delegate allocation
             //只是一个委托分配
            Action a = EmptyFunction;
            return;
            void EmptyFunction() { }
        }

    如果局部函数捕获局部变量/参数,将发生闭包分配和委托分配:

        public void Baz(int arg)
        {
            // Local function captures an enclosing variable.
            // The compiler will instantiate a closure and a delegate
            //本地函数捕获一个封闭的变量。
            //编译器将实例化一个闭包和一个委托
            Action a = EmptyFunction;
            return;
            void EmptyFunction() { Console.WriteLine(arg); }
        }
    • 本地函数捕获局部变量/参数,匿名函数从同一范围捕获变量/参数。

    这种情况更为微妙。

    C#编译器为每个词法范围生成一个不同的闭包类型(方法参数和顶级局部变量驻留在同一个顶级范围内)。在以下情况中,编译器将生成两个闭包类型:

        public void DifferentScopes(int arg)
        {
            {
                int local = 42;
                Func<int> a = () => local;
                Func<int> b = () => local;
            }
         
            Func<int> c = () => arg;
        }
    

    两个不同的Lambda表达式如果它们从同一范围捕获局部变量,将使用相同的闭包类型,Lambda ab驻留在同一闭包:

        private sealed class c__DisplayClass0_0
        {
            public int local;
         
            internal int DifferentScopes_b__0()
            {
                // Body of the lambda 'a'
                return this.local;
            }
         
            internal int DifferentScopes_b__1()
            {
                // Body of the lambda 'a'
                return this.local;
            }
        }
         
        private sealed class c__DisplayClass0_1
        {
            public int arg;
         
            internal int DifferentScopes_b__2()
            {
                // Body of the lambda 'c'
                return this.arg;
            }
        }
         
        public void DifferentScopes(int arg)
        {
            var closure1 = new c__DisplayClass0_0 { local = 42 };
            var closure2 = new c__DisplayClass0_1() { arg = arg };
            var a = new Func<int>(closure1.DifferentScopes_b__0);
            var b = new Func<int>(closure1.DifferentScopes_b__1);
            var c = new Func<int>(closure2.DifferentScopes_b__2);
        }

    在某些情况下,这种行为可能会导致一些非常严重的内存相关问题。这是一个例子:

        private Func<int> func;
        public void ImplicitCapture(int arg)
        {
            var o = new VeryExpensiveObject();
            Func<int> a = () => o.GetHashCode();
            Console.WriteLine(a());
         
            Func<int> b = () => arg;
            func = b;
        }

    在委托调用a()之后,变量o似乎应该符合垃圾回收的条件,但事实并非如此,两个Lambda表达式共享相同的闭包类型:

        private sealed class c__DisplayClass1_0
        {
            public VeryExpensiveObject o;
            public int arg;
         
            internal int ImplicitCapture_b__0()
                => this.o.GetHashCode();
         
            internal int ImplicitCapture_b__1()
                => this.arg;
        }
         
        private Func<int> func;
         
        public void ImplicitCapture(int arg)
        {
            var c__DisplayClass1_ = new c__DisplayClass1_0()
            {
                arg = arg,
                o = new VeryExpensiveObject()
            };
            var a = new Func<int>(c__DisplayClass1_.ImplicitCapture_b__0);
            Console.WriteLine(func());
            var b = new Func<int>(c__DisplayClass1_.ImplicitCapture_b__1);
            this.func = b;
        }

    这意味着闭包实例的生命周期将被绑定到func字段的生命周期:闭包保持活动,直到应用程序到达委托func。这可以延长VeryExpensiveObject生命周期,这基本上会导致内存泄漏。

    当局部函数和Lambda表达式捕获来自同一范围的变量时,会发生类似的问题。即使捕获不同的变量,封闭类型将被共享,导致堆分配:

        public int ImplicitAllocation(int arg)
        {
            if (arg == int.MaxValue)
            {
                // This code is effectively unreachable
                Func<int> a = () => arg;
            }
         
            int local = 42;
            return Local();
         
            int Local() => local;
        }

    编译为:

        private sealed class c__DisplayClass0_0
        {
            public int arg;
            public int local;
         
            internal int ImplicitAllocation_b__0()
                => this.arg;
         
            internal int ImplicitAllocation_g__Local1()
                => this.local;
        }
         
        public int ImplicitAllocation(int arg)
        {
            var c__DisplayClass0_ = new c__DisplayClass0_0 { arg = arg };
            if (c__DisplayClass0_.arg == int.MaxValue)
            {
                var func = new Func<int>(c__DisplayClass0_.ImplicitAllocation_b__0);
            }
            c__DisplayClass0_.local = 42;
            return c__DisplayClass0_.ImplicitAllocation_g__Local1();
        }

    正如您可以看到,顶层作用域中的所有局部变量现在都成为封闭类的一部分,即使当局部函数和Lambda表达式捕获不同的变量时也会导致关闭分配。

    总结

    以下是C#中关于局部函数的最重要特性:

    1. 局部函数可以定义迭代器;
    2. 局部函数对异步方法和迭代器块的预先验证非常有用;
    3. 局部函数可以递归;
    4. 如果没有向委托进行转换,局部函数是免分配的;
    5. 由于缺少委托调用开销,局部函数的效率比匿名函数稍高;
    6. 局部函数可以在返回语句之后声明,它将主逻辑与辅助函数分开;
    7. 局部函数可以“隐藏”在外部范围中声明的具有相同名称的函数;
    8. 局部函数可以使用async和/或unsafe修饰符,不允许使用其它修饰符;
    9. 局部函数无法使用属性;
    10. 局部函数在IDE中还不是非常友好:没有“提取局部函数重构”(目前为止),如果一个局部函数的代码被破坏了,您将在IDE中收到很多“波浪线”。

    这是Benchmark测试结果:

        private static int n = 42;
         
        [Benchmark]
        public bool DelegateInvocation()
        {
            Func<bool> fn = () => n == 42;
            return fn();
        }
         
        [Benchmark]
        public bool LocalFunctionInvocation()
        {
            return fn();
            bool fn() => n == 42;
        }
    Method Mean Error StdDev Allocated
    DelegateInvocation 2.3035 ns 0.0847 ns 0.0869 ns 0 B
    LocalFunctionInvocation 0.0142 ns 0.0176 ns 0.0137 ns 0 B

    不要因为差异而感到困惑,它看起来很大,但我几乎从来没有看到委托调用开销造成的真正问题。

    原文:《Dissecting the local functions in C# 7》https://blogs.msdn.microsoft.com/seteplia/2017/10/03/dissecting-the-local-functions-in-c-7/
    翻译:Sweet Tang
    本文地址:http://www.cnblogs.com/tdfblog/p/dissecting-the-local-functions-in-c-7.html
    欢迎转载,请在明显位置给出出处及链接。

    转载于:https://www.cnblogs.com/tdfblog/p/dissecting-the-local-functions-in-c-7.html

    展开全文
  • 编译器错误信息:CS0136:不能范围内声明名为“e”的局部变量,因为这样会使“e”具有不同的含义,而它已“父级或当前”范围中表示其他内容了 源错误: 行29:adapter.Fill(ds); 行30:} 行31:catch...
    编译器错误信息: CS0136: 不能在此范围内声明名为“e”的局部变量,因为这样会使“e”具有不同的含义,而它已在“父级或当前”范围中表示其他内容了

    源错误:

     

    行 29:             adapter.Fill(ds);
    行 30:         }
    行 31:         catch (SqlException e)
    行 32:         {
    行 33:             err = e.Message;

    原因是在aspx附带的cs中声明的,而在数据访问层DAL中的每一个方法都定义了SqlException类型的e,更个名就OK了~

    转载于:https://www.cnblogs.com/enterBeijingThreetimes/archive/2008/08/28/1278776.html

    展开全文
  • C#基础教程-c#实例教程,适合初学者。 第一章 C#语言基础 本章介绍C#语言的基础知识,希望具有C语言的读者能够基本掌握C#语言,并以此为基础,能够进一步学习用C#语言编写window应用程序和Web应用程序。当然仅靠一...
  • 以下将是 C# 7.0 中所有计划的语言特性的描述。随着 Visual Studio “15” Preview 4 版本的发布,这些特性中的大部分将活跃起来。现在是时候来展示这些特性,你也告诉借此告诉我们你的想法!
  • C#最新功能(6.0、7.0)

    2019-06-27 20:26:16
    一直用C#开发程序,.NET的功能越来越多,变化也挺大的,从最初的封闭,到现在的开源,功能不断的增加,一直进步。作为C#的强烈支持者,C#的变化,我不能不关注,这篇文章主要介绍,C#6.0和C#7.0增加的功能。C#的...
  • C#拾遗之类的OOP特性

    2015-04-10 22:09:10
    OOP概述  OOP不仅是一项具体的软件开发技术,而且是一整套关于如何看待软件系统与现实世界关系,以何种观点来研究问题并进行求解以及如何进行系统构造的软件方法学。  OOP方法的出现弥补了传统方法的不足,首先...
  • C#基础知识总结

    2018-03-15 18:40:19
    C#文件a.“.cs”文件C#语言源代码文件b.“.csproj”.csproj文件是.C#的工程文件,其中记录了与工程有关的相关信息,例如包含的文件,程序的版本,所生成的文件的类型和位置的信息等。c.“.sln”.sln文件是解决方案...
  • C# 3.0 语言规范

    2008-09-17 23:00:00
    C# 3.0 语言规范 从FxCop归纳出来的一些规范建议 一、 Design(设计)1. Abstract types should not have constructors 抽象类不应该声明构造方法2. Assemblies should have valid strong names 程序集应该具有强...
  • c#5.0/6.0/7.0

    2017-08-15 22:10:08
    发现很多.net 程序员水平一直停留在c#3.0阶段,现在来整理下c#5.0/6.0/7.0新语法新特性。 人生需要不断充电,不断去get新技能而不是固步自封,对于我自己而言,虽不盲目追求新技术,但每当有新技术出现时也会去...
  • c#边学边做

    2014-01-03 10:13:38
    -------------------------------------------------------------------------------------------------------------------------...开始学习C#,用C# using System; using System.Collections.Generic; using
  • C#泛型方法解析

    2017-03-03 15:17:41
    C#泛型方法解析  C#2.0引入了泛型这个特性,由于泛型的引入,一定程度上极大的增强了C#的生命力,可以完成C#1.0时需要编写复杂代码才可以完成的一些功能。但是作为开发者,对于泛型可谓是又爱又恨,爱的...
  • C#学习笔记

    2018-03-26 11:21:10
    :【文件】--》新建项目--》找到我们需要的项目类型新建项目窗口中,我们新建的项目要求可以这里选择:(1)版本选择:建议大家选择4或者4.6(2)选择开发语言:一般都是C#(3)项目类型:初学阶段一般都用...
  • C#_02_常用类型

    2018-05-17 09:55:44
    C# 编程_第02章_基础语法 C# 封装 封装 被定义为"把一个或多个项目封闭在一个物理的或者逻辑的包中"。面向对象程序设计方法论中,封装是为了防止对实现细节的访问。 抽象和封装是面向对象程序...
  • C#各版本新特性

    2019-06-12 11:55:02
    C# 2.0 C# 3.0 C# 5.0 C# 6.0 表达式 everywhere out变量 元组和解构 解构方法 Deconstrct 改造Size的构造方法 模式匹配 ref 局部变量和 ref 返回值 数字字面量语法增强 局部函数 支持更多 async 返回...
1 2 3 4 5 ... 20
收藏数 1,661
精华内容 664