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!)
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 theerror=trueflag), 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.dlland moving them all toPaintDotNet.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 moveTout of its assembly, because it's obsolete, then I cannot prune any assembly references thatTrequires.As an example, let's say that types
T1andT2are inAssembly1. I want to makeT1obsolete and move it intoAssembly1.Obsolete. I also want to moveT2intoAssembly2, and not haveAssembly1referenceAssembly2at all (1Assembly1.Obsolete1, however, would referenceAssembly2). 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!)