The following source generator creates a new record type on initialization.
It then selects the symbol for this type in another step, and generates a dummy file with a timestamp.
Since the underlying record type is only generated once on initialization, and does not change anymore thereafter, I would've expected the next transformation step to be invoked only once. However, it seems to be updated every time I type anything whatsoever in my IDE (Rider 2022.3.1), i.e. test2.g.cs is re-generated each time with an updated timestamp.
Why does that happen - and how can I prevent these unnecessary updates?
public class TestGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Create test1.g.cs on initialization
context.RegisterPostInitializationOutput(context => context.AddSource("test1.g.cs", "public record TestRecord();"));
// Find the type symbol for the previously generated record, and generate test2.g.cs based on this
context.RegisterSourceOutput(
context.SyntaxProvider.CreateSyntaxProvider(
static (context, _) => context is RecordDeclarationSyntax s && s.Identifier.Text.Equals("TestRecord"),
static (context, _) => context.SemanticModel.GetDeclaredSymbol(context.Node) as INamedTypeSymbol
).Where(x => x is not null),
(context, symbol) => context.AddSource("test2.g.gs", $"// Found: {symbol is not null} at {DateTime.UtcNow}")
);
}
}
Don't include symbols in the pipeline. They root compilations in memory, and won't compare equal across different compilations.
Instead, you should be creating a data model (with proper equality semantics - preferably with records). The data model should only include what you really need for generation, not including symbols, syntax nodes, compilation.
Turns out this was caused by referencing the analyzer project directly.
When packaging it into a NuGet package and referencing that, caching works as expected.
Related
I've got a similar problem like Oracle DataAccess related: "The invoked member is not supported in a dynamic assembly."
I've got the same Oracle exception ("the invoked member is not supported in a dynamic assembly").
However, I think the Oracle version is not the problem since the program worked fine before that, and the Oracle version is the same. It's related to the support of a custom oracle data type (SdoGeometry).
[OracleCustomTypeMappingAttribute("MDSYS.SDO_GEOMETRY")]
public class SdoGeometry : OracleCustomTypeBase<SdoGeometry> { ... }
Note: it worked well until now.
The program tries to get some data from an oracle database and computes and stores result in MongoDB database. Since there have been recent developments on the MongoDB part (what's the connection? I'm getting there), i'm getting the exception in some cases.
So, the program works as follow:
1. If there is data in MongoDB, the program checks it
2. The program selects data in Oracle and prepares it
3. It stores data in MongoDB
The (sometimes) failing operation is in step 2: it consists in adding colums type names in the output data, like this:
private void ReadTypes(Dictionary<string, string> types, DbDataReader reader){
if (types.Count == 0) {
int fieldCount = reader.FieldCount;
for (int i = 0; i < fieldCount; i++) {
string fieldName = reader.GetName(i);
try {
string fieldType = reader.GetFieldType(i).Name;
types[fieldName] = fieldType;
}
catch (Exception e) {
// the invoked member is not supported in a dynamic assembly
// only for SdoGeometry type
}
}
}
}
OracleDataReader.GetFieldType(i) fails for SdoGeometry type (our custom data type) only when step 1 is executed (there is some MongoDB operations). I've identified the responsible operation, it's:
mongoEvents = mongoEvents_.Find(e => e.Identifier.Table.Equals(table)).ToList();
(by moving an if-true-return block from line to line - on a part of the application that I did not show - I identified that it was this operation that produced the error).
This operation consists in extracting from Mongo, the data already archived for the current Oracle table. It's an operation for MongoAPI (IMongoCollection.Find). So:
If I comment this line and return an empty list (or with a manually inserted object), there is no more exception. Well...
But what is strange is this:
//I've replaced the previous statement.
//It's working, no mongo data is returned but this is independent of step 2,
//which in any case retrieves data from Oracle database.
//(MongoDataEvent is one of our classes which defines the structure of archived data)
mongoEvents = new List<MongoEvent.MongoDataEvent>();
Okay, but if instead of that, I add this statment after the previous one:
mongoEvents = mongoEvents_.Find(e => e.Identifier.Table.Equals(table)).ToList();
mongoEvents = new List<MongoEvent.MongoDataEvent>();
Okay it's useless, but when emptying the list after performing the Find method, the exception appears again (not while calling Find, but after while calling GetFieldType), while the list is empty.
So...I don't have any idea what's going on. Any ideas ? Thanks.
I found a solution !
By simply adding, in the first statement of my program, an Oracle (dummy) query making a select on a SdoGeometry type field of an arbitrarily chosen table.
I think this forces the SdoGeometry type to load.
So as I understand it the bug was happening while querying MongoDb data before loading Oracle SdoGeometry data, there was a problem loading in assembly (?).
Still, this solution works perfectly! Every time I try it works and as soon as I try to comment out the request forcing SdoGeometry to load, the error always occurs again. Additionally, the MongoDb data looks correct.
I don't understand everything in the details but it works !
I'm trying to come up with a Resharper pattern to apply to sequential Shouldly asserts.
For instance, I have these checks:
field1.ShouldNotBeNull();
field2.ShouldBe(expectedField2Value);
And in this case, it should be replaced with:
this.ShouldSatisflyAllConditions(
() => field1.ShouldNotBeNull(),
() => field2.ShouldBe(expectedField2Value));
And there's no problem if this was the only case, but the thing is that there are a lot of different possibilities that aren't covered in the patterns I managed so far. What I'm trying to do is to get to the point where anytime I get two or more sequential checks of any kind (ShouldBeNull, ShouldNotBeNull, ShouldContain, etc) I'd be warned to put all of those inside a ShouldSatisfyAllConditions block, since if I keep it as individual asserts, the tests will stop running as soon as one failed, instead of giving me a list of failures in the latter case.
The problems I'm facing are:
I've been unable to use any field name in the pattern. When I select
and "Search with Pattern", I just get the names of those specific
fields, not applicable to the whole project.
EDIT: My mistake. The pattern I tried is applied anytime two fields with any name are asserted to not null.
I've been unable to apply this with any of the Shouldly asserts and I have to put it as a case-by-case (such as the example above). I only get specific patterns instead of a more generic one.
Besides the very specific cases, I can't apply this for anytime there's two or more cases. I have to select each pattern, 2 lines, 3 lines and so on.
I'm using VS 17 Enterprise and JetBrains ReSharper Ultimate 2019.2.2.
This:
$field1$.ShouldNotBeNull();
$field2$.ShouldNotBeNull();
Replaced with this:
this.ShouldSatisfyAllConditions(
() => $pageStyleProperty$.ShouldNotBeNull(),
() => $_editorObject$.ShouldNotBeNull());
If you really want to do this you can with a params Action[]
public void ShouldSatisfyAllConditions(params Action[] assertions)
{
foreach (var assertion in assertions)
{
assertion?.Invoke();
}
}
Consider this simple console application:
class Program
{
static void Main(string[] args)
{
var human = CreateHuman(args[0]);
Console.WriteLine("Created Human");
Console.ReadLine();
}
public static object CreateHuman(string association)
{
object human = null;
if (association == "is-a")
{
human = new IsAHuman();
}
else
{
human = new HasAHuman();
}
return human;
}
}
public class IsAHuman : Human
{
}
public class HasAHuman
{
public Human Human { get; set; }
}
The Human class is in another assembly, say HumanAssembly.dll. If HumanAssembly.dll exists in the bin directory of our console app, everything would be fine. And as we might expect, by removing it we encounter FileNotFoundException.
I don't understand this part though. Comment human = new IsAHuman(); line, recompile and remove HumanAssembly.dll. Console app won't throw any exception in this case.
My guess is that CLR compiler differentiates between is a and has a associations. In other words, CLR tries to find out and understand and probably load all the types existing in the class definition statement, but it can instantiate a class without knowing what's inside it. But I'm not sure about my interpretation.
I fail to find a good explanation. What is the explanation for this behavior?
You are seeing the behavior of the JIT compiler. Just In Time. A method doesn't get compiled until the last possible moment, just before it is called. Since you removed the need to actually construct a Human object, there is no code path left that forces the jitter to load the assembly. So your program won't crash.
The last remaining reference to Human is the HashAHuman.Human property. You don't use it.
Predicting when the jitter is going to need to load an assembly is not that straight-forward in practice. It gets pretty difficult to reason through when you run the Release build of your code. That normally enables the optimizer that's built into the jitter, one of its core optimization strategies is to inline a method. To do that, it needs access to the method before it is called. You'd need an extra level of indirection, an extra method that has the [MethodImpl(MethodImplOptions.NoInlining)] attribute to stop it from having a peek. That gets to be a bit off into the deep end, always consider a plug-in architecture first, something like MEF.
Here is great explanation of what you are looking for.
The CLR Loader
Specially in the following lines -
This policy of loading types (and assemblies and modules) on demand means that parts of a program that are not used are never brought into
memory. It also means that a running application will often see new
assemblies and modules loaded over time as the types contained in
those files are needed during execution. If this is not the behavior
you want, you have two options. One is to simply declare hidden static
fields of the types you want to guarantee are loaded when your type is
loaded. The other is to interact with the loader explicitly.
As the Bold line says, if you code does not execute a specific line then the types won't be loaded, even if the code is not commented out.
Here is also a similar answer that you might also be interested in -
How are DLLs loaded by the CLR?
Foreword: I am trying to describe the scenario very precisely here. The TL;DR version is 'how do I tell if a lambda will be compiled into an instance method or a closure'...
I am using MvvmLight in my WPF projects, and that library recently changed to using WeakReference instances in order to hold the actions that are passed into a RelayCommand. So, effectively, we have an object somewhere which is holding a WeakReference to an Action<T>.
Now, since upgrading to the latest version, some of our commands stopped working. And we had some code like this:
ctor(Guid token)
{
Command = new RelayCommand(x => Messenger.Default.Send(x, token));
}
This caused a closure (please correct me if I'm not using the correct term) class to be generated - like this:
[CompilerGenerated]
private sealed class <>c__DisplayClass4
{
public object token;
public void <.ctor>b__0(ReportType x)
{
Messenger.Default.Send<ReportTypeSelected>(new ReportTypeSelected(X), this.token);
}
}
This worked fine previously, as the action was stored within the RelayCommand instance, and was kept alive whether it was compiled to an instance method or a closure (i.e. using the '<>DisplayClass' syntax).
However, now, because it is held in a WeakReference, the code only works if the lambda specified is compiled into an instance method. This is because the closure class is instantiated, passed into the RelayCommand and virtually instantly garbage collected, meaning that when the command came to be used, there was no action to perform. So, the above code has to be modified. Changing it to the following causes that, for instance:
Guid _token;
ctor(Guid token)
{
_token = token;
Command = new RelayCommand(x => Messenger.Default.Send(x, _token));
}
This causes the compiled code to result in a member - like the following:
[CompilerGenerated]
private void <.ctor>b__0(ReportType x)
{
Messenger.Default.Send<ReportTypeSelected>(new ReportTypeSelected(X), this._token);
}
Now the above is all fine, and I understand why it didn't work previously, and how changing it caused it to work. However, what I am left with is something which means the code I write now has to be stylistically different based on a compiler decision which I am not privy to.
So, my question is - is this a documented behaviour in all circumstances - or could the behaviour change based on future implementations of the compiler? Should I just forget trying to use lambdas and always pass an instance method into the RelayCommand? Or should I have a convention whereby the action is always cached into an instance member:
Action<ReportTypeSelected> _commandAction;
ctor(Guid token)
{
_commandAction = x => Messenger.Default.Send(x, token);
Command = new RelayCommand(_commandAction);
}
Any background reading pointers are also gratefully accepted!
Whether you will end up with a new class or an instance method on the current class is an implementation detail you should not rely on.
From the C# specification, chapter 7.15.2 (emphasis mine):
It is explicitly unspecified whether there is any way to execute the block of an anonymous function other than through evaluation and invocation of the lambda-expression or anonymous-method-expression. In particular, the compiler may choose to implement an anonymous function by synthesizing one or more named methods or types.
-> Even the fact that it generates any methods at all is not specified.
Given the circumstances, I would go with named methods instead of anonymous ones. If that's not possible, because you need to access variables from the method that registers the command, you should go with the code you showed last.
In my opinion the decision to change RelayCommand to use WeakReference was a poor one. It created a lot more problems than it solved.
As soon as the lambda references any free variables (aka capture), then this will happen as it needs a common location (aka storage class/closure) to reference (and/or assign to) them.
An exercise for the reader is to determine why these storage classes cannot just be static.
I have a WCF service, hosted in IIS. This service is defined by a generic interface with interface-types as arguments or return types, and so we make use of the ServiceKnownType attribute to define the available implementations of said interfaces at runtime.
This all seems to work fine, however on occasion, we see all requests to these services fail with CommunicationException; "There was an error while trying to serialize parameter http://tempuri.org/:arg. The InnerException message was 'Type 'MyNamespace.SomeInterfaceImplementation' with data contract name 'SomeInterfaceImplementation:http://schemas.datacontract.org/2004/07/MyNamespace' is not expected. Add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.'. Please see InnerException for more details."
I am unable to reliably reproduce this error, however it regularly appears after the services have been left running for some time (e.g; over the weekend). I initially speculated that this was occurring due to IIS App Pool recycling, however manual recycling, or scheduled recycling on small intervals (e.g; 2 minutes) fails to reproduce the problem.
I am able to reliably reproduce the exception by excluding 'MyNamespace.MyType' from the provider of 'ServiceKnownTypes', however that doesn't really tell me why this is occurring intermittently in the first place.
The following snippet shows the service declaration. It is a generic service for different implementation types of . Note that it is the operation argument 'arg' which is producing the CommunicationException.
[ServiceKnownType("GetKnownTypes", typeof(KnownTypesCache))]
[ServiceContract]
public interface IMyService<T>
where T : class, IMyServiceTypeInterface
{
[OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)]
MyReturnType HandleRequest(T element, ISomeInterfaceArgumentType arg);
}
Now, the implementation of KnownTypesCache, which provides the known types for ISomeInterfaceArgumentType is like so;
public static class KnownTypesCache
{
private static readonly List<Assembly> queriedAssemblies = new List<Assembly>();
private static readonly List<Type> knownTypes = new List<Type>();
static KnownTypesCache()
{
// get all available assemblies at this time
List<Assembly> assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
// find all available known types publishers
IEnumerable<Type> knownTypesPublisherTypes = assemblies
.Where(a => !queriedAssemblies.Contains(a)) // exclude already queried assemblies to speed things up
.SelectMany(s => s.GetTypes())
.Where(p => typeof(IKnownTypesPublisher).IsAssignableFrom(p) && p.HasAttribute(typeof(KnownTypesPublisherAttribute)));
// add all known types
foreach (Type type in knownTypesPublisherTypes)
{
IKnownTypesPublisher publisher = (IKnownTypesPublisher)Activator.CreateInstance(type);
AddRange(publisher.GetKnownTypes());
}
}
// record the assemblies we've already loaded to avoid relookup
queriedAssemblies.AddRange(assemblies);
}
public static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider)
{
return knownTypes;
}
}
Basically, this global static cache queries all loaded assemblies in the AppDomain for implementors of 'IKnownTypesPublisher', which in turn provide available serialization types for each assembly. So, there is one IKnownTypesPublisher per assembly, which is responsible for identifying types in that assembly only, and the KnownTypesCache simply aggregates all of these and returns to the DataContractSerializer at runtime.
As I mentioned, this approach seems to work fine 99% of the time. And then for no reason I can identify, it stops working and can only be resolved by a call to iisreset.
I am pretty stumped now, I have tried all sorts of solutions, but since I can only reliably reproduce this error by waiting until first thing Monday, it's a bit tough!
My last remaining thought is that the static KnownTypesCache constructor might be being called before all assemblies are loaded to the AppDomain, and so the cache will be empty for the life of the instance...?
My last remaining thought is that the static KnownTypesCache
constructor might be being called before all assemblies are loaded to
the AppDomain
With this, you are probably well on the way to answering your own question.
AppDomain.GetAssemblies() only returns those assemblies which have already been loaded into the execution context of the AppDomain. After any recycling of the IIS worker process, which creates a new AppDomain, you have a potential race condition between the first use of the KnownTypesCache type, and the loading of any assembly containing one of your IKnownTypesPublisher types. Intermittent, hard-to-reproduce bugs, can frequently be caused by race conditions.
For your design to work correctly, you would need to ensure somehow that the service implementation always loads all the assemblies containing your known types, before anything calls a KnownTypesCache method.