-1

When writing classes for internal processing in .Net, I often use ArgumentException to indicate something is wrong with the given data and it can't be processed. Due to the nature of the program, the text I put in these exceptions is sometimes relevant to the user, and so it often gets shown on the UI.

However, I noticed that ArgumentException specifically overrides the Message property to append its own string to indicate which argument caused the exception. I don't want this extra text polluting the message, since the actual argument name is internal processing info that really doesn't need to be shown to the user, and the fact it adds a line break, and that it is localised, messes up the formatting of the message I show on the UI. The only way to get around this is to not give the exception the actual argument name, but I don't want to sabotage my own debugging / logging by removing that information, either.

I could use my own exception class, of course, but since a lot of these methods are for compression and decompression of proprietary file formats in old DOS games, and I want these methods to both be documented on a wiki and be generally easily usable by anyone else, I'd prefer keeping them portable and avoid reliance on other external classes. And, as a side note, subclassing ArgumentException would of course give the same issue.

The original source:

public override String Message
{
    get {
        String s = base.Message;
        if (!String.IsNullOrEmpty(m_paramName)) {
            String resourceString = Environment.GetResourceString("Arg_ParamName_Name", m_paramName);
            return s + Environment.NewLine + resourceString;
        }
        else
            return s;
    }
}

(from referencesource.microsoft.com)

Since this actually overrides the Message property, there seems to be no normal way to get to the real message that's stored internally. Splitting on a line break seems messy and potentially unreliable depending on localisation differences (and the message I give it might potentially have line breaks already), and using reflection for this seems rather messy. Is there a clean way to recover the original message?

(Posting this here with solution for documenting reasons, since this behaviour really frustrated me when I encountered it)

Nyerguds
  • 5,360
  • 1
  • 31
  • 63
  • I don't get your point on "relying on external classes". Either use an `ArgumentException` for something that **is** an argument, or your own exception-class. What is the problem on `catch(MyOwnException)`? – MakePeaceGreatAgain Sep 02 '21 at 09:03
  • This issue is that I wanted to keep the code portable and easy for other people to use. And I only realised the issue after using `ArgumentException` extensively. – Nyerguds Sep 02 '21 at 09:04
  • But users need your own code anyway, so why not also ship that extra class? Anyway: you may also use `InvalidOperationException`. – MakePeaceGreatAgain Sep 02 '21 at 09:04
  • 1
    This makes no sense. First of all, you're relying on exceptions for non-exceptional cases, which is seen as an anti-pattern depending on who you ask. Secondly, it makes no sense at all that you "don't want to use your own exceptions". Just create exception classes that correctly identify what happened through simple names and that's it. – Camilo Terevinto Sep 02 '21 at 09:05
  • How is "the file contains bad data for this function to process/decompress" not an exception case? This post was mostly made out of frustration to why a .net standard exception class would mangle the exception message put into it by the programmer. – Nyerguds Sep 02 '21 at 09:06
  • How is "the file contains bad data for this function to process" an Argument exception? That looks like an InvalidOperationException, or even better, a custom InvalidFileException/CorruptedFileException. Argument exceptions *are for* programmers, not for end users – Camilo Terevinto Sep 02 '21 at 09:08
  • it does so, because you should use it for errorous **arguments**. However you use it for something completely different. – MakePeaceGreatAgain Sep 02 '21 at 09:09
  • I'm fairly sure the data given to a function is, in fact, an argument. And if something inside that is invalid, it's an invalid argument. – Nyerguds Sep 02 '21 at 09:09
  • no, on a very abstract base it's just data that is *referred by* by some argument. In fact the data does not even need any argument at all. – MakePeaceGreatAgain Sep 02 '21 at 09:11
  • ArgumentException and its children are for ensuring that arguments pass comply to basic rules (not null, greater than zero, etc), and their intent is mostly to tell developers (or yourself) "you forgot to pass this value!". Complex business logic validation shouldn't be argument exceptions – Camilo Terevinto Sep 02 '21 at 09:11
  • The official info disagrees. It says the exception is for an argument that "does not meet the parameter specification of the called method". Their actual example is an integer number not being even and thus not being exactly divisible by 2. My example is byte data not conforming to an expected compression scheme and thus being impossible to decompress. That seems pretty equivalent to me. – Nyerguds Sep 02 '21 at 09:14
  • From the docs: "If the method call does not have any argument or **if the failure does not involve the arguments themselves, then InvalidOperationException should be used.**". The file-**content** pretty sure is not equivalent to the **name** of the file, is it? So it's pretty clear not an error of the passed **argument**, but of the underlying data **represented** by that argument. – MakePeaceGreatAgain Sep 02 '21 at 09:18
  • The function decompresses _data_. It gets no file name. It gets a byte array filled with data that was taken from somewhere inside a file. – Nyerguds Sep 02 '21 at 09:19
  • You´re essentially mixing to things here: the argument -e.g a file-name - does not have any **data**, it just **points** to some data. The data may be corrupt, but that doesn´t mean anything to the pointer, which is still valid. – MakePeaceGreatAgain Sep 02 '21 at 09:20
  • The actual _full_ processing of the file formats indeed uses its own custom exceptions, but I'm not talking about that. I'm only talking about the decompression method used _in_ that file processing. I want those custom exceptions to get the unmangled message out of the _decompression function_, which is its own thing. And the argument given to the decompression function is _exactly and only_ the actual byte data to decompress. – Nyerguds Sep 02 '21 at 09:20
  • OK, let's come to your actual question. Why is `catch(MyException)` any less portable and usable to others? You have to ship your program in some way, haven't you? So you can also ship that extra class. – MakePeaceGreatAgain Sep 02 '21 at 09:25
  • You misunderstand. The code for my program is irrelevant. I am documenting / distributing the _decompression methods specifically_ independently. And the real point of the question is to document undesired behaviour in .net default classes. (In fact, I'd argue that _all_ automatically-localised error messages in .net classes are undesired, but that's a different discussion) – Nyerguds Sep 02 '21 at 09:27
  • Maybe I do: What exactly do you mean by "distribute the method"? Don't you deploy your decrompression-method within any kind of assembly? If not, **how** do you ship it? – MakePeaceGreatAgain Sep 02 '21 at 09:29
  • As I specifically said in the actual post, I'm documenting the actual decompression methods as code snippets on a wiki. Not an ideal format to provide custom exception classes, I'd say, but these exceptions still need to be thrown, and as per my arguments above, `ArgumentException` is the most logical default .Net class to use for them. My own tool is mostly a side effect of the research, hence why I'm displaying these messages to the user. – Nyerguds Sep 02 '21 at 09:32

1 Answers1

0

Since I didn't want to dig into reflection, I figured a good way to get the original data without the associated class behaviour would be to serialize it. The names of the properties in the serialised info are very straightforward, and can be accessed without the ArgumentException getter mangling it with its own additions.

The code to accomplish this turned out to be pretty straightforward:

public static String RecoverArgExceptionMessage(ArgumentException argex)
{
    if (argex == null)
        return null;
    SerializationInfo info = new SerializationInfo(typeof(ArgumentException), new FormatterConverter());
    argex.GetObjectData(info, new StreamingContext(StreamingContextStates.Clone));
    return info.GetString("Message");
}
Nyerguds
  • 5,360
  • 1
  • 31
  • 63