Skip to content

Please allow [assembly: TypeForwardedTo(typeof(T))] when T is [Obsolete] #61264

@rickbrew

Description

@rickbrew

I've been doing a lot of cleanup and refactoring in my app's codebase (Paint.NET) over the last several months (well, slowly over years, rapidly over the last 6 months). It's approximately 600,000 lines of code and has acquired a lot of cruft. A lot of that cruft can be deleted, as it's internal stuff.

However, some of that cruft is/was used by plugins. Sometimes because it was an official API that I was providing, other times because it was something I neglected to mark as internal (there's a whole story there... 🤦‍♂️). I cannot / will not delete these classes because I take backwards compatibility very seriously. I'm not interested in breaking someone's workflow because their favorite plugin was using an API that I want to sweep away, sometimes just for aesthetic reasons; I don't want to make it my user's/customer's problem. [Obsolete("...", true)] is a great tool for this.

I'm now finding that certain types of refactoring/cleanup are hitting another wall: I can move types to lower-layer assemblies by using [assembly: TypeForwardedTo(typeof(T))] and this works great ...except for obsolete types (with the error=true flag), because it results in a compile-time error that cannot be suppressed. This prevents me from taking a bunch of old classes out of e.g. PaintDotNet.Effects.dll and moving them all to PaintDotNet.Effects.Obsolete.dll. I want to do this to prevent new plugins from using them, and also to sweep them out of the way entirely (no Intellisense, no docs, etc.). But, old plugins would still continue to function (an absolute requirement).

In addition, the inability to use TypeForwardedTo(typeof(T)) on an obsolete type prevents me from doing other types of cleanup, such as dropping assembly references. If I can't move T out of its assembly, because it's obsolete, then I cannot prune any assembly references that T requires.

As an example, let's say that types T1 and T2 are in Assembly1. I want to make T1 obsolete and move it into Assembly1.Obsolete. I also want to move T2 into Assembly2, and not have Assembly1 reference Assembly2 at all (1Assembly1.Obsolete1, however, would reference Assembly2). Right now I can't do that, and it's causing some inflexibility in how I can refactor things.

There are two workarounds that I can think of for this right now. The first is to inject the [assembly: TypeForwardTo(...)] attributes as some kind of post-build event, e.g. using Mono.Cecil. That's way overkill and I shouldn't have to do that. The other is to use [Obsolete("...", false)], but that doesn't solve the problem of preventing new plugins from using those types (plugin authors will do this, they don't care 🤣 You should see the stuff I've seen in plugins ... dreadful, awful stuff!)

Metadata

Metadata

Assignees

Labels

4 - In ReviewA fix for the issue is submitted for review.Area-CompilersConcept-APIThis issue involves adding, removing, clarification, or modification of an API.Feature Request

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions