C#中的异常安全 [英] Exception Safety in C#

查看:73
本文介绍了C#中的异常安全的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

您好:

作为C ++书籍的狂热读者,我知道很多程序员在那里

面临着异常安全的挑战。


对于那些不知道它是什么的人来说,它是以这样的方式编写代码

异常不会导致类成为离开了一个不稳定的
状态。例如,你不希望OutOfMemoryException离开

a容器,其大小无效或缺少元素。


现在,在C ++中,主要思想是编写代码,这样你就可以尽可能多地工作,而不做任何可能导致和异常的事情,

并且如果出现问题则回溯。在C ++中,实现者

通常通过使用一个名为copy / modify / swap的成语来实现这一点,以实现

异常安全。


我想知道在C#中这样做是否更容易。 C#中的赋值与C ++中用于引用类型的赋值具有不同的含义。

赋值使得事物相等,而不是等同。 C#原生支持

Clone(),但不是真正的复制ctor。由于C#使用了引用,我不能确定
真的改变了我的引用,因为多个引用可能指向

我正在修改的对象(有时候)。


想象一下,有一个课程看起来像这样:


class Dog

{

private int age = 0;

//。 。 。

public void CelebrateBirthday()

{

++年龄;

//。 。 。此时抛出异常

}

}


如果我执行此代码并抛出异常,我的狗

将比他真正应该的年龄大一岁(假设我想要回滚(撤消)面对异常时的变化)。一个

解决方案看起来像这样:


public void CelebrateBirthday()

{

试试

{

++年龄;

//。 。 。此时抛出异常

}

catch

{

--age;

//撤消对其他字段的潜在更改(可能需要存储

旧值)

}

}


现在,我必须在我班上的每个变异方法/属性

周围写一下。 YUCK!在一个有几十个领域的课堂上,这将是一场噩梦。


这是我提出的解决方案,类似于C ++的做法:


私人无效副本(狗狗)

{

age = dog.age;

/ /转移额外的字段

}


public void CelebrateBirthday()

{

Dog tempDog = new Dog();

tempDog.copy(this);

++ tempDog.age;

//。 。 。此时抛出异常

this.copy(tempDog);

}


请注意,不再有需要尝试/捕获。如果出现问题

错误,副本tempDog就会浪费掉。最后一行是

随着变化更新''this''。


现在,这对我来说似乎很简单。我很难相信

某人没有想出更通用或更正确的东西(我认为

我的是正确的???)。例如,有很多值得关注的问题

关于深/浅拷贝。


社区范围的方法是我正在寻找的。任何人?


谢谢,

Travis Parks

Hello:

As an avid reader of C++ books, I know a lot of programmers out there
are confronted with the challenge of exception safety.

For those who don''t know what it is, it is writing code in such a way
that an exception will not cause a class to be left in an unstable
state. For instance, you wouldn''t want an OutOfMemoryException leaving
a container with an invalid size or missing elements.

Now, in C++, the main idea is to write code so that you do as much
work as possible withouth doing anything that can cause and exception,
and back track if something does go wrong. In C++, implementors
usually do this by using an idiom called, copy/modify/swap, to achieve
exception safety.

I am wondering if this is easier to do in C#. Assignment in C# has a
different meaning than assignment in C++ for reference types.
Assignment makes things equal, not equivelent. C# natively supports
Clone(), but not really a copy ctor. Since C# uses references, I can''t
really change my reference since multiple references may be pointed to
the object I am modifying (some times).

Imagine there is a class that looks something like this:

class Dog
{
private int age = 0;
// . . .
public void CelebrateBirthday()
{
++age;
// . . . something throws an exception at this point
}
}

If I were to execute this code and an exception was thrown, my dog
would be a year older than he really should be (assuming that I want
to rollback (undo) the change in the face of an exception). One
solution would look like this:

public void CelebrateBirthday()
{
try
{
++age;
// . . . something throws an exception at this point
}
catch
{
--age;
// undo potential changes to other fields (may require storing
the old value)
}
}

Now, I would have to write that around every mutating method/property
in my class. YUCK! In a class with dozens of fields, that would be a
nightmare.

This is my proposed solution, similar to how it is done is C++:

private void copy(Dog dog)
{
age = dog.age;
// transfer additional fields
}

public void CelebrateBirthday()
{
Dog tempDog = new Dog();
tempDog.copy(this);
++tempDog.age;
// . . . something throws an exception at this point
this.copy(tempDog);
}

Notice that there is no longer a need for try/catch. If something goes
wrong, the copy, tempDog, just gets wasted. The final line is to
update ''this'' with the changes.

Now, this seems really simple to me. I have trouble believing that
someone didn''t come up with something more general or correct (I think
mine is correct???). For instance, there is a lot to be concerned
about with deep/shallow copies.

Community-wide approaches are what I am looking for. Anyone?

Thanks,
Travis Parks

推荐答案

2007年6月28日星期四18:54:44 -0700, je ********** @ gmail.com

< je ********** @ gmail.comwrote:
On Thu, 28 Jun 2007 18:54:44 -0700, je**********@gmail.com
<je**********@gmail.comwrote:

[...]

请注意,不再需要try / catch。如果出现问题

错误,副本tempDog就会浪费掉。最后一行是

随着变化更新''this''。


现在,这对我来说似乎很简单。我很难相信

某人没有想出更通用或更正确的东西(我认为

我的是正确的???)。例如,有很多值得关注的问题

关于深/浅拷贝。
[...]
Notice that there is no longer a need for try/catch. If something goes
wrong, the copy, tempDog, just gets wasted. The final line is to
update ''this'' with the changes.

Now, this seems really simple to me. I have trouble believing that
someone didn''t come up with something more general or correct (I think
mine is correct???). For instance, there is a lot to be concerned
about with deep/shallow copies.



恐怕我真的不明白这个问题。该技术 -

创建一个临时实例,其中一个操作然后以某种方式替换原始版本的临时版本 - 你描述的是一般的

并且很普遍。它适用于许多场景,而不仅仅是在OOP

类中(例如,写入临时文件,只删除

原文并重命名临时文件如果临时文件成功并且完全创建了




也就是说,C ++本身并不支持这种技术,而不仅仅是C#

。所以我真的不明白你似乎在C ++和C#之间做出的比较




至于这项技术是否合适,我会说很多

的时间不是。这实际上取决于你在做什么,但创建一个实例的完整副本,修改,然后将所有这些复制回来原来有一个实际的副本

很多开销本身需要实现复制()方法或类似的东西
。我还没有碰到一个

的情况,我觉得如果发生异常,只需做一下我需要做的任何清理是不合理的。


另请注意,在您的示例中,您仍然需要在某处捕获

异常。是的,你可以抓住它并且

只是忽略它。但是如果你不得不编写异常

处理程序,为什么不让它做一些有用的事情,比如在

之后清理代码中包含的任何操作正在做什么?


对于适合这种技术的那些时间,那么肯定......我会同意

它是的,好吧。 ..适当。作为标准事项,我会将它写入我的每一个

类吗?不。


Pete

I''m afraid I don''t really understand the question. The technique -- to
create a temporary instance on which one operates and then somehow replace
the original with the temporary version -- you describe is both general
and widespread. It applies in a number of scenarios, and not just in OOP
classes (for example, writing to a temp file, and only deleting the
original and renaming the temp file if the temp file is successfully and
completely created).

That said, C++ does not inherently support this technique any more than C#
does. So I don''t really understand the comparison you seem to be making
between C++ and C#.

As far as whether the technique is appropriate generally, I''d say much of
the time it''s not. It really depends on what you''re doing, but creating a
complete copy of an instance, modifying, and then copying all of that back
to the original has a lot of overhead and itself requires implementation
of the Copy() method or something like it. I have yet to run into a
situation where I felt it was unreasonable to simply do whatever cleanup I
need to do locally should an exception occur.

Note also that in your example, you sort of gloss over the fact that the
exception still needs to be caught somewhere. Yes, you can catch it and
just ignore it. But if you''re going to have to write the exception
handler anyway, why not have it do something useful, like clean up after
whatever operation the code contained within was doing?

For those times when the technique is appropriate, then sure...I''d agree
that it''s, well...appropriate. Would I write it into every one of my
classes as a standard matter? Nope.

Pete


6月28日晚上8:48,Peter Duniho < NpOeStPe ... @nnowslpianmk.com>

写道:
On Jun 28, 8:48 pm, "Peter Duniho" <NpOeStPe...@nnowslpianmk.com>
wrote:

2007年6月28日星期四18:54:44 -0700 ,jehugalea ... @ gmail.com

On Thu, 28 Jun 2007 18:54:44 -0700, jehugalea...@gmail.com

<jehugalea...@gmail.comwrote:

[。 ..]

请注意,不再需要try / catch。如果出现问题

错误,副本tempDog就会浪费掉。最后一行是

随着更改更新''this''。
[...]
Notice that there is no longer a need for try/catch. If something goes
wrong, the copy, tempDog, just gets wasted. The final line is to
update ''this'' with the changes.


现在,这对我来说似乎很简单。我很难相信

某人没有想出更通用或更正确的东西(我认为

我的是正确的???)。例如,有很多值得关注的问题

关于深/浅拷贝。
Now, this seems really simple to me. I have trouble believing that
someone didn''t come up with something more general or correct (I think
mine is correct???). For instance, there is a lot to be concerned
about with deep/shallow copies.



我害怕我真的不明白这个问题。该技术 -

创建一个临时实例,其中一个操作然后以某种方式替换原始版本的临时版本 - 你描述的是一般的

并且很普遍。它适用于许多场景,而不仅仅是在OOP

类中(例如,写入临时文件,只删除

原文并重命名临时文件如果临时文件成功并且完全创建了




也就是说,C ++本身并不支持这种技术,而不仅仅是C#

。所以我真的不明白你似乎在C ++和C#之间做出的比较




至于这项技术是否合适,我会说很多

的时间不是。这实际上取决于你在做什么,但创建一个实例的完整副本,修改,然后将所有这些复制回来原来有一个实际的副本

很多开销本身需要实现复制()方法或类似的东西
。我还没有碰到一个

的情况,我觉得如果发生异常,只需做一下我需要做的任何清理是不合理的。


另请注意,在您的示例中,您仍然需要在某处捕获

异常。是的,你可以抓住它并且

只是忽略它。但是如果你不得不编写异常

处理程序,为什么不让它做一些有用的事情,比如在

之后清理代码中包含的任何操作正在做什么?


对于适合这种技术的那些时间,那么肯定......我会同意

它是的,好吧。 ..适当。作为标准事项,我会将它写入我的每一个

类吗?不。


Pete


I''m afraid I don''t really understand the question. The technique -- to
create a temporary instance on which one operates and then somehow replace
the original with the temporary version -- you describe is both general
and widespread. It applies in a number of scenarios, and not just in OOP
classes (for example, writing to a temp file, and only deleting the
original and renaming the temp file if the temp file is successfully and
completely created).

That said, C++ does not inherently support this technique any more than C#
does. So I don''t really understand the comparison you seem to be making
between C++ and C#.

As far as whether the technique is appropriate generally, I''d say much of
the time it''s not. It really depends on what you''re doing, but creating a
complete copy of an instance, modifying, and then copying all of that back
to the original has a lot of overhead and itself requires implementation
of the Copy() method or something like it. I have yet to run into a
situation where I felt it was unreasonable to simply do whatever cleanup I
need to do locally should an exception occur.

Note also that in your example, you sort of gloss over the fact that the
exception still needs to be caught somewhere. Yes, you can catch it and
just ignore it. But if you''re going to have to write the exception
handler anyway, why not have it do something useful, like clean up after
whatever operation the code contained within was doing?

For those times when the technique is appropriate, then sure...I''d agree
that it''s, well...appropriate. Would I write it into every one of my
classes as a standard matter? Nope.

Pete



我原来的帖子要求社区范围内的异常方法

安全。我并不真正关心

语言之间的差异,或者复制/修改/替换成语如何适用于计算机科学的各个方面。我只是想知道开发人员为解决问题所采取的其他措施。


我不同意应该利用catch块来撤消

尝试尝试。这可能是最有效的手段之一。

然而,程序员通常都很健忘。必须在可能的许多catch块中控制

深度的代码是一个维护

的噩梦。您可能会创建一个重置

值的方法。但是你需要知道将它们重置为什么。所以你需要

来存储这些值。


我只是建议(因为将相关数据分组是有意义的)

创建一个副本。你不必用try / catch

来捣乱你的代码。而且我并不太关心处理异常。如此,我甚至不想让我的代码*显示*关注

他们;它应该做正确的事情。


我不认为copy()方法的实现会是一个很大的b / b
家务。你看到它可以相当容易。你只是保存你的
状态。作业分配。 。 。


现在这里是困境。 。 。因为我们创建了一个类的副本,所以我们不能调用任何创建我们类副本的方法,而不需要创建冗余副本。所以我们必须假设我们只是直接处理字段或者不是例外的方法

安全。这似乎是一个巨大的限制。但是,我认为这提供了一个绝佳的机会!我认为这表明我们创建了

接口方法和实现方法,其中一个简单地将

委托给另一个。然后我们可以正式化复制/修改/替换

流程。


公共类狗

{

public void CelebrateBirthday()

{

Dog tempDog = new tempDog(); //所有公共修改方法

以相同的方式开始和结束

tempDog.celebrateBirthday(); //实现方法

this.copy(tempDog);

}

}


现在个人而言,我不认为复制是一个很大的浪费。我们只讨论典型类的几个字节(大多数情况下需要存储
才能撤消更改)。如果我们愿意的话,我们甚至可能会获得
甚至可以使用reduntant副本。这取决于我们关于效率的方式。如果我们真的想要,我们甚至可以使用公共字段将类状态作为一个私有结构,并让我们自己打开以进入状态模式的领域(如果需要)和

只需复制/修改/替换它。


我们需要以某种方式存储原始状态。我们最初创建了用于封装该数据的
。为什么不使用它?


现在,根据我的经验,我很少在本地处理异常 - 我

我不知道怎么做;它并不总是合理的/可能的。最多我会用
使用finally块来清理。但是,清理总是需要

的地方,所以我不能放一个撤消的地方。在这里运作。我觉得愚蠢的交易

清理每次捕获甚至一次捕获。你的时间旅行狗

看起来像这样:


public void CelebrateBirthday()

{

int ageChange = new Random()。Next(10); //对于可以前往过去的狗来说,-1是什么意思?

尝试

{

age + = ageChange;

}

catch

{

age - = ageChange;

}

}


我的版本长约三行。关于ageChange的范围,我不需要担心

;我不需要处理进入我的时间旅行狗的所有其他

变量。没错,我只需要
存储我改变的变量,但我怎么知道其中一种方法

我的方法里面不会有一天改变以改变其他方法?其他

程序员不会理解你为什么要拥有所有额外的b / b
代码行,只是为了给一只可怜的狗的生命增加几年。我们在哪里创建一个实现方法,代码很简单,因为

它会删除所有异常处理。它只是按照它所说的那样做了
而没有别的。由于我们可以在每个公共方法中正式化复制/修改/替换

,我的程序员只需记住一件事

而不需要理解(他们几乎可以忘记,像大多数程序员所做的那样关于异常安全。如果效率绝对是至关重要的,那么你的方法就是纯粹的肾上腺素。但总的来说,

这种方法更具可读性和可维护性,并且几乎没有更低的效率。


让我知道我很紧张。我想我有一些东西

不仅仅是偶然的;对于任何异常安全的
类,我认为这是合适的

(并不总是最有效)。我认为应该在分析器有b $ b表示你​​应该提高效率之后。这一切都归结为我的朋友的记忆;这只是一个很大的问题。


~Travis

My original post asks for a community-wide approach to exception
safety. I wasn''t really concerned with the differences between
languages or how the copy/modify/replace idiom applies to various
other aspects of computer science. I simply want to know what other
developers have done to face the problem.

I don''t agree that catch blocks should be utilized to undo what the
try was trying to. It is probably one of the most efficient means.
However, programmers are generally forgetful. Having to control that
depth of code inside potentially many catch blocks is a maintenance
nightmare. You would probably create a single method that resets the
values. But then you need to know what to reset them to. So you need
to store those values somewhere.

I am just suggesting (as it makes sense to group related data) to
create a copy. You don''t have to muddy up your code with try/catch
then. And I am not all too concerned about handling the exceptions. As
such, I don''t even want to make my code *appear* to be concerned about
them; it should just do the right thing.

I don''t think the implementation of a copy() method would be a big
chore. You saw that it can be fairly easy. You are just saving your
state. assignment assignment assignment . . .

Now here is the dilemma . . . since we created a copy of our class, we
cannot call any methods that create a copy of our class without
redundant copies being made. So we have to assume that we are only
dealing with the fields directly or methods that are not exception
safe. It seems like a huge limitation. However, I think this presents
a wonderful opportunity! I think this suggests that we create
interface methods and implementation methods, where one simply
delegates to the other. We can then formalize the copy/modify/replace
process.

public class Dog
{
public void CelebrateBirthday()
{
Dog tempDog = new tempDog(); // all public modifying methods
begin and end the same way
tempDog.celebrateBirthday(); // implementation method
this.copy(tempDog);
}
}

Now, personally, I do not think copying is a large waste. We are
talking about just a few bytes for typical classes (most of which
would need to be stored to undo changes anyway). We could probably
even get away with reduntant copies if we wanted to. It depends how
anal we are about efficiency. If we truly wanted, we could even make
the state of the class a private struct with public fields and leave
ourselves open to enter the realm of the state pattern (if needed) and
just copy/modify/replace it.

We need to store the original state somehow, somewhere. We originally
created the class to enclose that data. Why not use it?

Now, from my experience, I rarely deal with exceptions locally - I
don''t know how to; it''s not always plausible/possible. At most I will
use a finally block to "clean up". However, clean up always takes
place, so I can''t put an "undo" operation here. I feel stupid dealing
with clean up in each catch or even one catch. Your time-traveling Dog
ends up looking like this:

public void CelebrateBirthday()
{
int ageChange = new Random().Next(10); // what does -1 mean for a
Dog that can travel to the past?
try
{
age += ageChange;
}
catch
{
age -= ageChange;
}
}

My version would be about three lines long. I don''t need to worry
about the scope of ageChange; I don''t need to handle all the other
variables that go into my time-travelling Dog. True, I only need to
store the variables I change, but how do I know one of the methods
inside my method won''t change someday to alter additional ones? Other
programmers aren''t going to understand why you have all the additional
lines of code, just to add a few years to a poor dog''s life. Where we
create an implementation method, the code is straight forward because
it removes all exception handling. It just does what it says it does
and nothing else. Since we can formalize the copy/modify/replace in
every public method, my programmers only need to remember one thing
and not need to understand (they can practically forget, like most
programmers do anyway) about exception safety. If efficiency is
absolutely critical, your method is pure adrenaline. But in general,
this approach is more readable and more maintainable, and hardly less
efficient.

Let me know if I am getting on your nerves. I think I have something
that is not just occassionally appropriate; I think it is appropriate
(not always the most efficient) for just about any exception-safe
class. I think efficiency gains should be made after a profiler has
said you should. It all comes down to a memcopy my friend; it just a
matter of how big the loop is.

~Travis




" Peter Duniho" < Np ********* @nnowslpianmk.com在留言中写道

news:op *************** @ petes-computer.local ...

"Peter Duniho" <Np*********@nnowslpianmk.comwrote in message
news:op***************@petes-computer.local...

2007年6月28日星期四18:54:44 -0700, je ********** @ gmail.com

< je ********** @ gmail。 comwrote:
On Thu, 28 Jun 2007 18:54:44 -0700, je**********@gmail.com
<je**********@gmail.comwrote:

> [...]
请注意,不再需要try / catch。如果出现问题,那么副本tempDog就会被浪费掉。最后一行是用更改来更新''this'。

现在,这对我来说似乎很简单。我很难相信某人没有想出更通用或更正确的东西(我认为我的是正确的???)。例如,关于深/浅拷贝有很多值得关注的问题。
>[...]
Notice that there is no longer a need for try/catch. If something goes
wrong, the copy, tempDog, just gets wasted. The final line is to
update ''this'' with the changes.

Now, this seems really simple to me. I have trouble believing that
someone didn''t come up with something more general or correct (I think
mine is correct???). For instance, there is a lot to be concerned
about with deep/shallow copies.



我害怕我真的不明白这个问题。该技术 -

创建一个临时实例,其中一个操作然后以某种方式替换原始版本的临时版本 - 你描述的是一般的

并且很普遍。它适用于许多场景,而不仅仅是在OOP

类中(例如,写入临时文件,只删除

原文并重命名临时文件如果临时文件成功并且完全创建了




也就是说,C ++本身并不支持这种技术,而不仅仅是C#

。所以我真的不明白你似乎要做的比较


I''m afraid I don''t really understand the question. The technique -- to
create a temporary instance on which one operates and then somehow replace
the original with the temporary version -- you describe is both general
and widespread. It applies in a number of scenarios, and not just in OOP
classes (for example, writing to a temp file, and only deleting the
original and renaming the temp file if the temp file is successfully and
completely created).

That said, C++ does not inherently support this technique any more than C#
does. So I don''t really understand the comparison you seem to be making



当然可以,用编译器生成的默认运算符=。

Sure it does, with compiler-generated default operator=.

C ++和C#之间的



至于这项技术是否合适,我会说很多

时间不是。这实际上取决于你在做什么,但创建一个实例的完整副本,修改,然后将所有这些复制回来原来有一个实际的副本

很多开销本身需要实现复制()方法或类似的东西
。我还没有碰到一个

的情况,我觉得如果发生异常,只需做一下我需要做的任何清理是不合理的。


另请注意,在您的示例中,您仍然需要在某处捕获

异常。是的,你可以抓住它并且
between C++ and C#.

As far as whether the technique is appropriate generally, I''d say much of
the time it''s not. It really depends on what you''re doing, but creating a
complete copy of an instance, modifying, and then copying all of that back
to the original has a lot of overhead and itself requires implementation
of the Copy() method or something like it. I have yet to run into a
situation where I felt it was unreasonable to simply do whatever cleanup I
need to do locally should an exception occur.

Note also that in your example, you sort of gloss over the fact that the
exception still needs to be caught somewhere. Yes, you can catch it and



唯一需要捕获的地方是一层代码可以*修复*

(告诉用户保存在其他地方,重试上传等)。任何

的低级代码都只需要重新抛出它,最好不要抓住它/ b $ b(并弄乱堆栈跟踪)。是的,即使是重新渲染

也会弄乱堆栈跟踪。

The only place it needs to be caught, is at a layer of code that can *fix*
it (Tell the user to save somewhere else, retry the upload, etc). Any
low-level code is just going to have to rethrow it, better to not catch it
(and mess up the stack trace) in the first place. And yes, even a rethrow
messes up the stack trace.


只是忽略它。但是如果你不得不编写异常

处理程序,为什么不让它做一些有用的事情,比如在

之后清理代码中包含的任何操作正在做什么?


对于适合这种技术的那些时间,那么肯定......我会同意

它是的,好吧。 ..适当。作为标准事项,我会将它写入我的每一个

类吗?不。
just ignore it. But if you''re going to have to write the exception
handler anyway, why not have it do something useful, like clean up after
whatever operation the code contained within was doing?

For those times when the technique is appropriate, then sure...I''d agree
that it''s, well...appropriate. Would I write it into every one of my
classes as a standard matter? Nope.



嗯,C ++程序员几乎总能提供支持。他们要么接受编译器生成的运算符= b $ b,编写自己的运算符,要么明确禁用

(这是罕见的)赋值。

Hmm, C++ programmers do provide support nearly always. They either accept
the compiler-generated operator=, write their own, or explicitly disable
(and this is rare) assignment.


>

Pete
>
Pete


这篇关于C#中的异常安全的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆