C# 'CompanyName.Foo' 是一个“命名空间”,但它的使用方式就像一个“类型”

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2046012/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 22:58:40  来源:igfitidea点击:

'CompanyName.Foo' is a 'namespace' but is used like a 'type'

c#linq-to-sqlcompiler-constructioncode-generationnamespaces

提问by devuxer

Restatement of the question

问题的重述

I'm resurrecting this question because I just ran into this error again today, and I'm still utterly confused why the C# compiler bothers to check for collisions between namespaces and types in contexts where it makes no sense for a namespace to exist.

我重新提出这个问题,因为我今天又遇到了这个错误,我仍然完全困惑为什么 C# 编译器费心检查命名空间存在没有意义的上下文中的命名空间和类型之间的冲突。

If I have...

如果我有...

public Foo MyFoo { get; set; }

...why would the compiler care that Foois both a namespace and a type? Can you declare a property as a namespace instead of a type?

...为什么编译器会关心Foo它既是命名空间又是类型?您可以将属性声明为命名空间而不是类型吗?

What is the logic behind the "namespace used like type" compiler error? What problem is this saving me from?

“namespace used like type”编译器错误背后的逻辑是什么?这让我免于什么问题?

[And how do I tag Eric Lippert? :)]

[我如何标记埃里克·利珀特?:)]



Original Question

原始问题



The problem

问题

I have a project "Foo" with default namespace CompanyName.Foo. I have a database that's also called "Foo".

我有一个默认命名空间的项目“Foo” CompanyName.Foo。我有一个也称为“Foo”的数据库。

And when I run SqlMetal.exe on the database, it generates a class CompanyName.Foo.Models.Foo.

当我在数据库上运行 SqlMetal.exe 时,它​​会生成一个类CompanyName.Foo.Models.Foo.

Then, when I attempt to create a property with this class as the type, like this...

然后,当我尝试使用此类作为类型创建属性时,就像这样......

using CompanyName.Foo.Models;
...
public Foo DataContext { get; set; }

...I get the error:

...我收到错误:

'CompanyName.Foo' is a 'namespace' but is used like a 'type'.

'CompanyName.Foo' 是一个“命名空间”,但它的使用方式类似于“类型”。

I am forced to do...

我被迫做...

public CompanyName.Foo.Models.Foo Foo { get; set; } // :-(

Questions:

问题:

  1. Why does this error occur? My property declaration doesn't contain CompanyName, so why is this a problem? Simply put: Foo != CompanyName.Foo. Also, just to be sure, I did a search of my entire solution for namespace Fooand came up with zero hits (if I had actually used a namespace Foo, I could understand getting an error).

  2. [answered]Is there any way around fully qualifying Fooevery time I want to use it?

  3. [answered]Is there any way to get SqlMetal to name the class anything other than Foo(w/o changing the name of my database)? I can change the namespace using a switch, but I don't know of a way to change the actual class name.

  1. 为什么会出现这个错误?我的财产声明不包含CompanyName,为什么这是一个问题?简单地说:Foo != CompanyName.Foo。另外,可以肯定的是,我搜索了我的整个解决方案namespace Foo并得出了零命中(如果我实际上使用了 namespace Foo,我可以理解出现错误)。

  2. [已回答]Foo每次我想使用它时,有没有办法完全排位赛?

  3. [已回答]除了Foo(不更改我的数据库的名称)之外,还有什么方法可以让 SqlMetal 为类命名?我可以使用开关更改命名空间,但我不知道更改实际类名的方法。

Update

更新

Still seeking an answer to (1).

仍在寻求(1)的答案。

O.K.W. nailed (2) & (3).

OKW 钉 (2) & (3)。

Usings

用途

A request was made for all my usingstatements:

对我所有的using陈述提出了要求:

using System;
using System.ComponentModel;
using System.Data.Linq;
using System.Linq;
using MyCompany.Foo.Models;

采纳答案by Eric Lippert

how do I tag Eric Lippert?

我如何标记埃里克·利珀特?

If you have something you want brought to my attention you can use the "contact" link on my blog.

如果您想引起我的注意,可以使用我博客上的“联系方式”链接。

I'm still utterly confused why the C# compiler bothers to check for collisions between namespaces and types in contexts where it makes no sense for a namespace to exist.

我仍然非常困惑,为什么 C# 编译器要在命名空间存在没有意义的上下文中检查命名空间和类型之间的冲突。

Indeed, the rules here are tricky. Coincidentally, two weeks ago I wrote and posted a series of blog articles about some of these issues closely related to this very issue; they'll actually go live in early March. Watch the blog for details.

确实,这里的规则很棘手。巧合的是,两周前,我写并发布了一系列博客文章,讨论与这个问题密切相关的一些问题;他们实际上会在三月初上线。观看博客了解详情。

UPDATE: The articles mentioned above are here:

更新:上面提到的文章在这里:

http://blogs.msdn.com/b/ericlippert/archive/tags/namespaces/

http://blogs.msdn.com/b/ericlippert/archive/tags/namespaces/

Why does this error occur?

为什么会出现这个错误?

Let me rephrase the question into several questions.

让我把这个问题改写成几个问题。

What sections of the specification justify the production of this error?

规范的哪些部分证明了这个错误的产生?

I think that's already been covered satisfactorily in other answers. The type resolution algorithm is extremely well-specified. But just to sum up: being insidesomething of the right name "binds more tightly" than usingsomething of the right name from the outside. When you say:

我认为其他答案已经令人满意地涵盖了这一点。类型解析算法是非常明确的。但总而言之:与使用正确名称的外部事物相比,在具有正确名称的事物内部“结合得更紧密” 。当你说:

using XYZ;
namespace ABC.DEF
{
    class GHI : DEF { }
}

that is the same as

这与

using XYZ;
namespace ABC
{
    namespace DEF 
    {
        class GHI : DEF { }
    }
}

So now we must determine the meaning of DEF. We go from inside to outside. Is there a type parameter of GHI called DEF? No. Look at the container. Is there a member of DEF called DEF? No. Look at the container. Is there a member of ABC called DEF? YES. We're done; we have determined the meaning of DEF, it is a namespace. We discover the meaning of DEF beforewe ask "does XYZ have a member DEF?"

所以现在我们必须确定DEF的含义。我们从内到外。GHI 是否有一个称为 DEF 的类型参数?不。看容器。有DEF的成员叫DEF吗?不。看容器。ABC有成员叫DEF吗?是的。我们完成了;我们已经确定了 DEF 的含义,它是一个命名空间。我们问“XYZ 有成员 DEF 吗?”之前,我们发现了 DEF 的含义。

What design principles influence this design?

哪些设计原则会影响这种设计?

One design principle is "names mean the same thing no matter how you use them". The language does not 100% obey this principle; there are situations in which the same name can be used to refer to two different things in the same code. But in general, we strive for a situation where when you see "Foo" two times in the same context, it means the same thing. (See my article on The Color Color Problemfor some details on this, as well as my articles on identifying violations of the "simple name" rules.)

一个设计原则是“无论您如何使用名称,名称都具有相同的含义”。语言并不是 100% 遵守这个原则;在某些情况下,可以使用相同的名称来指代同一代码中的两个不同的事物。但总的来说,我们力求实现这样一种情况:当您在同一上下文中看到两次“Foo”时,它的含义是相同的。(有关这方面一些详细信息,请参阅我关于颜色颜色问题的文章,以及我关于识别违反“简单名称”规则的文章。)

One design principle is "no backtracking". We do not ever say in C# "I see that you used a name to refer to something that is not legal to refer to in this context. Let me abandon the result of name binding and start over, looking for something that might work."

一种设计原则是“无回溯”。我们从来没有在 C# 中说“我看到你使用了一个名称来指代在这种情况下不合法的东西。让我放弃名称绑定的结果并重新开始,寻找可能有用的东西。”

A larger principle that underlies the "no backtracking" principle is that C# is not a "guess what the user meant" language. You wrote a program where the best possible binding of an identifier identified a namespace when a type was expected. There are two possibilities. Possibility one: you've made an error that you want to be told about so that youcan take action to correct it. Possibility two: you meant for a less-good binding to be the one we choose, and so we should guessfrom amongst all the possible less-good bindings to figure out which one you probably meant.

“无回溯”原则背后的一个更大的原则是,C# 不是“猜测用户的意思”语言。您编写了一个程序,其中最好的标识符绑定在预期类型时标识了一个命名空间。有两种可能。可能性一:你犯了一个错误,你想被告知,以便可以采取行动纠正它。可能性二:你的意思是我们选择一个不太好的绑定,所以我们应该从所有可能的不太好的绑定中猜测你可能指的是哪一个。

That's a good design principle in languages like JScript -- JScript is all about muddling on through when the developer does something crazy. C# is not that kind of language; the feedback we get loud and clear from our developers is tell me when something is broken so I can fix it.

这是像 JScript 这样的语言的一个很好的设计原则——JScript 就是在开发人员做了一些疯狂的事情时糊涂。C# 不是那种语言;我们从开发人员那里得到的响亮而清晰的反馈是告诉我什么时候出现问题,以便我可以修复它

The thing about "no backtracking" is that it makes the language much easier to understand. Suppose you have something like this mess:

关于“无回溯”的事情是它使语言更容易理解。假设你有这样的烂摊子:

namespace XYZ.DEF
{
    public class GHI {}
}
namespace QRS.DEF.GHI
{
    public class JKL { }
}
...
using QRS;
namespace TUV 
{
  using XYZ;
  namespace ABC
  {
    namespace DEF 
    {
        class GHI { }
        class MNO : DEF.GHI.JKL { }
    }
  }
}

Work out the base type of MNO. With no backtracking we say "DEF is ABC.DEF". Therefore GHI is ABC.DEF.GHI. Therefore JKL is ABC.DEF.GHI.JKL, which does not exist, error. You must fix the error by giving a type name that lets the compiler identify which DEF you meant.

确定 MNO 的基本类型。没有回溯,我们说“DEF 是 ABC.DEF”。因此 GHI 是 ABC.DEF.GHI。所以JKL是ABC.DEF.GHI.JKL,不存在,报错。您必须通过提供一个类型名称来修复错误,以便编译器识别您所指的 DEF。

If we had backtracking, what would we have to do? We get that error, and then we backtrack. Does XYZ contain a DEF? Yes. Does it contain a GHI? Yes. Does it contain a JKL? No. Backtrack again. Does QRS contain an DEF.GHI.JKL? Yes.

如果我们有回溯,我们必须做什么?我们得到那个错误,然后我们回溯。XYZ 是否包含 DEF?是的。它是否包含 GHI?是的。它包含 JKL 吗?不。再次回溯。QRS 是否包含 DEF.GHI.JKL?是的。

That works, but can we logically conclude from the fact that it works that it is the one the user meant?

这是有效的,但是我们能否从它有效的事实中合乎逻辑地得出结论,它就是用户所指的那个?

Who the heck knows in this crazy siutation? We got all kinds of good bindings in there that then went bad very late in the game. The idea that we stumbled upon the desired answer after going down many blind alleys seems highly suspect.

在这种疯狂的情况下,谁知道呢?我们在那里获得了各种良好的绑定,然后在游戏后期就变坏了。我们在走上许多死胡同后偶然发现了想要的答案的想法似乎非常值得怀疑。

The correct thing to do here is not to backtrack multiple times and try out all kinds of worse bindings for every stage of the lookup. The correct thing to do is to say "buddy, the best possible match for this lookup gives nonsensical results; give me something less ambiguous to work with here please."

这里正确的做法是不要多次回溯并在查找的每个阶段尝试各种更糟糕的绑定。正确的做法是说“伙计,这个查找的最佳匹配给出了无意义的结果;请给我一些不那么模棱两可的东西在这里工作。”

An unfortunate fact about writing a language where the compiler by designcomplains loudly if the best match is something that doesn't work, is that developers frequently say "well, sure, in generalI want the compiler to point out all my mistakes -- or, rather, all my coworker's mistakes. But for this specificcase, I know what I am doing, so please, compiler, do what I mean, not what I say."

关于编写一种语言,如果最佳匹配不起作用,那么设计编译器会大声抱怨的语言的不幸事实是,开发人员经常说“好吧,当然,总的来说,我希望编译器指出我所有的错误——或者,更确切地说,是我同事的所有错误。但对于这个特定案例,我知道我在做什么,所以请编译器,按我的意思做,而不是我说的。”

Trouble is, you can't have it both ways. You can't have both a compiler that both enforces rigid rules that make it highly likely that suspicious code will be aggressively identified as erroneous andallow crazy code via compiler heuristics that figure out "what I really meant" when you write something that the compiler quite rightly sees as ambiguous or wrong.

问题是,你不能同时拥有它。你不能同时拥有一个既执行严格规则的编译器,这使得可疑代码很可能被积极地识别为错误,允许通过编译器启发式的疯狂代码,当你编写一些编译器时找出“我真正的意思”完全正确地认为模棱两可或错误。

For an object lesson in how lots of pro devs vehemently dislike the effects of a language design that aggressively identifies errors rather than guessing that the developer meant for the worse result to be chosen, see the 116 comments to this article on a minor and rather unimportant aspect of overload resolution:

有关许多专业开发人员如何强烈不喜欢积极识别错误而不是猜测开发人员打算选择更糟糕的结果的语言设计的效果的客观课程,请参阅本文的 116 条评论,关于一个次要且相当不重要的重载决议的方面

(Note that I am no longer responding to comments on this issue; I've explained my position over ten times. If all those explanations are not convincing, that's because I'm not a very good convincer.)

(请注意,我不再回应关于这个问题的评论;我已经解释了十次我的立场。如果所有这些解释都不能令人信服,那是因为我不是一个很好的说服者。)

And finally, if you really want to test your understanding of how the name resolution rules work in C#, try out this little puzzle. Almost everyone gets it wrong, or gets it right for the wrong reasons. The answer is here.

最后,如果您真的想测试您对 C# 中名称解析规则如何工作的理解,请尝试这个小谜题。几乎每个人都做错了,或者因为错误的原因而做对了。答案在这里

回答by o.k.w

  1. The clash is between namespace CompanyName.Fooand CompanyName.Foo.Models.Foo, and not Foo. I'm not exactly sure how/why the compiler can't distinguish both though.

  2. You can try using namespace aliasto shorten full qualifying Foo
    e.g. using coyModels = CompanyName.Foo.Models

  3. From the reference, seems like you can use /context:<type>and /namespace:<name>to specify the data context class (instead of using table name) and namespace.

  1. 冲突发生在 namespaceCompanyName.Foo和之间CompanyName.Foo.Models.Foo,而不是Foo。我不确定编译器如何/为什么不能区分两者。

  2. 您可以尝试使用命名空间别名来缩短完全限定,Foo
    例如using coyModels = CompanyName.Foo.Models

  3. 参考资料来看,您似乎可以使用/context:<type>/namespace:<name>来指定数据上下文类(而不是使用表名)和命名空间。

回答by Igor Zevaka

C# compiler doesn't compile when there is an ambiguity between a class and a namespace with the same name. Unfortunately you just have to namespace the class explicitly or rename the database. In your case the compiler didn't even get to the conflict, it died after resolving Fooas a namespace.

当类和具有相同名称的命名空间之间存在歧义时,C# 编译器不会编译。不幸的是,您只需要显式命名该类或重命名数据库。在您的情况下,编译器甚至没有解决冲突,它在解析Foo为命名空间后就死了。

Whenever you have something like this:

每当你有这样的事情:

using CompanyName.Foo.Models;

namespace CompanyName.Foo {
    class Test {
        public Foo Model { get; set; } // error CS0118: 'CompanyName.Foo' is a 'namespace' but is used like a 'type'
        public Foo1 Model { get; set; } //OK
    }
}

namespace CompanyName.Foo.Models {
    class Foo1 {
    }
    class Foo {
    }
}

What actually happens is every preceeding level of the namespace is implicitly imported at each level. This makes sense since the nested namespace syntax using dot is the same as nesting namespaces:

实际发生的是命名空间的每个前面的级别都在每个级别隐式导入。这是有道理的,因为使用点的嵌套命名空间语法与嵌套命名空间相同:

namespace CompanyName {
    using CompanyName; //<--using1 - Implicit using, since we should be able to access anything within CompanyName implicitly.
    namespace Foo {
        using CompanyName.Foo; //<-- using2 Same as above
        class Test {
            public Foo Model { get; set; } //At this stage due to using1 Foo is actually CompanyName.Foo, hence the compiler error
        }
    }
}

So inside class Test there are two implicit usings:

所以在 Test 类内部有两个隐式using

using CompanyName;
using CompanyName.Foo;

Hence Foois resolved to the namespace hence the error.

因此Foo被解析为命名空间,因此出现错误。

EDITGood point. I've dug this up from MSDN:

编辑好点。我从MSDN 上挖到了这个:

The meaning of a namespace-or-type-name is determined as follows:

  • If the namespace-or-type-name consists of a single identifier:

    • If the namespace-or-type-name appears within the body of a class or struct declaration, then starting with that class or struct declaration and continuing with each enclosing class or struct declaration (if any), if a member with the given name exists, is accessible, and denotes a type, then the namespace-or-type-name refers to that member. Note that non-type members (constants, fields, methods, properties, indexers, operators, instance constructors, destructors, and static constructors) are ignored when determining the meaning of a namespace-or-type-name.

    • Otherwise, starting with the namespace in which the namespace-or-type-name occurs, continuing with each enclosing namespace (if any), and ending with the global namespace, the following steps are evaluated until an entity is located:

    • If the namespace contains a namespace member with the given name, then the namespace-or-type-name refers to that member and, depending on the member, is classified as a namespace or a type.

    • Otherwise, if the namespace has a corresponding namespace declaration enclosing the location where the namespace-or-type-name occurs, then:

    • If the namespace declaration contains a using-alias-directivethat associates the given name with an imported namespace or type, then the namespace-or-type-name refers to that namespace or type.

    • Otherwise, if the namespaces imported by the using-namespace-directives of the namespace declaration contain exactly one type with the given name, then the namespace-or-type-name refers to that type.
      ...

命名空间或类型名称的含义确定如下:

  • 如果 namespace-or-type-name 由单个标识符组成:

    • 如果命名空间或类型名称出现在类或结构声明的主体中,则从该类或结构声明开始,并继续每个封闭类或结构声明(如果有),如果存在具有给定名称的成员, 是可访问的,并且表示一个类型,那么 namespace-or-type-name 指的是那个成员。请注意,在确定命名空间或类型名称的含义时,将忽略非类型成员(常量、字段、方法、属性、索引器、运算符、实例构造函数、析构函数和静态构造函数)。

    • 否则,从出现 namespace-or-type-name 的命名空间开始,继续每个封闭的命名空间(如果有),并以全局命名空间结束,评估以下步骤,直到找到一个实体:

    • 如果命名空间包含具有给定 name 的命名空间成员,则 namespace-or-type-name 引用该成员,并根据成员被分类为命名空间或类型。

    • 否则,如果命名空间有一个相应的命名空间声明,包含命名空间或类型名称出现的位置,则:

    • 如果命名空间声明包含将给定名称与导入的命名空间或类型相关联的using-alias-directive,则 namespace-or-type-name 指的是该命名空间或类型。

    • 否则,如果命名空间声明的 using-namespace-directives 导入的命名空间只包含一种具有给定名称的类型,则 namespace-or-type-name 指的是该类型。
      ...

(Bolding is mine) This means that when resolving Foo, matching it against CompanyName.Foo(first bold bit) happens before matching it against the usingdirective(second bold build).

(粗体是我的)这意味着在解析时Foo,将它与CompanyName.Foo(第一个粗体位)匹配发生在将它与using指令(第二个粗体构建)匹配之前。

回答by alpha

why can't you just do

你为什么不能做

using CompanyName.Foo; 
... 
public Models.Foo DataContext { get; set; } 

回答by scottm

Because you've used dot notation to separate Company and Foo, you are implicitly creating a Company namespace, with a nested Foo namespace, not Company.Foo as you believe.

因为您已经使用点符号来分隔 Company 和 Foo,所以您隐式地创建了 Company 命名空间,其中包含一个嵌套的 Foo 命名空间,而不是您认为的 Company.Foo。

That's why this doesn't work:

这就是为什么这不起作用:

namespace Company.Foo
{
}

namespace Company.Foo.Models
{
    public class TestClass {
        public Foo DataContext { get; set; }
    }
}

The closest thing to Foois the nested Foo namespace in the Company namespace. You can however do this:

最接近的Foo是 Company 命名空间中嵌套的 Foo 命名空间。但是,您可以这样做:

using Company.Foo;
using Company.Foo.Models;

namespace Company.Foo
{
    class Program {
        public static void Main() {}
    }
}

namespace Company.Foo.Models
{
    public class Foo { }

}

public class DataContextClass
{
    public Foo DataContext { get; set; } /* Foo here is automatically resolved to Company.Foo.Models.Foo */
}

Edit

编辑

Igor said the same thing, but was more technical.

伊戈尔说了同样的话,但更具技术性。

回答by David Marchelya

I had this issue pop up when I was referencing a class in a separate class library, where its type had the same name as the root of the namespace. Initially, when referencing this type in a separate console app project, there was no problem, everything compiled fine. However the reference from a Windows Service project was generating the is a 'namespace' but is used like a 'type'.message. Turns out the Windows Service Project had its Target Framework set to ".NET Framework 4 Client Profile". Changing this to ".NET Framework 4" eliminated the error. Hopefully this helps someone in a similar situation.

当我在单独的类库中引用一个类时,我弹出了这个问题,其中它的类型与命名空间的根具有相同的名称。最初,在单独的控制台应用程序项目中引用此类型时,没有问题,一切都编译得很好。但是,来自 Windows 服务项目的引用正在生成is a 'namespace' but is used like a 'type'.消息。原来 Windows 服务项目的目标框架设置为“.NET Framework 4 Client Profile”。将此更改为“.NET Framework 4”消除了错误。希望这可以帮助处于类似情况的人。

回答by Jason Axelson

This also happens if you generate unit tests when you have a namespace and a class with the same name. Which you should never do as explained by Eric Lippert here:

如果在具有名称空间和具有相同名称的类时生成单元测试,也会发生这种情况。正如 Eric Lippert 在这里解释的那样,你永远不应该这样做:

http://blogs.msdn.com/b/ericlippert/archive/2010/03/09/do-not-name-a-class-the-same-as-its-namespace-part-one.aspx

http://blogs.msdn.com/b/ericlippert/archive/2010/03/09/do-not-name-a-class-the-same-as-its-namespace-part-one.aspx

回答by Tim H.

I am new to c# and I came into contact with this error upon decompiling a c# application, saving as a project, the attempting to immediately recompile... why the application was able to compile in the first place is beyond me.. however... the problem and solution is quite simple: by default, upon adding a new class, c# uses the same name for a namespace as it does for the class within the namespace!!!!! This is bad because without some hidden identifier explicitly telling which (namespace or type) you are referring to, the compiler can't tell the difference!!!!! doh! way to go c#!!!!... THE SOLUTION: Instead of renaming a thousand things and double checking all corrections, run the project, when you have the list of errors in front of you, click each in turn to go to each problem. Once at the "foo" in question type a dot (.) after said "foo" such that it displays: foo. .. this should bring up the menu of classes contained within. In this list, double-click "foo" (or just retype the name) changing the original "foo" to "foo.foo" ... Do this for each error and problem solved!!! Voila!!!! I did this to an entire application with complex names, and it worked great! Happy coding! - Tim H.

我是 c# 新手,在反编译 ac# 应用程序、保存为项目、尝试立即重新编译时遇到了这个错误......为什么应用程序能够首先编译是我无法理解的......但是。 .. 问题和解决方案很简单:默认情况下,在添加新类时,c# 对命名空间使用与命名空间中的类相同的名称!!!!!!这很糟糕,因为如果没有一些隐藏的标识符明确地告诉你指的是哪个(命名空间或类型),编译器就无法区分!!!!!!!哦!去c#的路!!!!... 解决方案:与其重命名一千件事并仔细检查所有更正,不如运行该项目,当您面前有错误列表时,依次单击每个以转到每个问题。一旦在有问题的“foo”处键入一个点 (.) 在说“foo”之后它会显示:foo。.. 这应该会调出其中包含的类菜单。在此列表中,双击“foo”(或只是重新键入名称)将原始“foo”更改为“foo.foo”...对每个错误和问题都执行此操作!!!瞧!!!!我对具有复杂名称的整个应用程序执行了此操作,并且效果很好!快乐编码!- 蒂姆 H.