- 
                Notifications
    You must be signed in to change notification settings 
- Fork 1.4k
StronglyTypedResourceBuilder output should be strongly typed #4588
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
StronglyTypedResourceBuilder output should be strongly typed #4588
Conversation
579ce4b    to
    161660b      
    Compare
  
    c4cef31    to
    9cf2500      
    Compare
  
    | I just rewrote this including the things we discussed. Annoyingly, I think a downcast to  In this iteration, I enabled  With a private build of this, I'm able to build WPF successfully without any workarounds applied to the repo. | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ericstj I'd love your input on this aspect in particular. Right now it results in generated code like
    /// <summary>
    ///   Looks up a localized resource of type System.String.
    /// </summary>
    internal static string String1 {
        get {
            object obj = ResourceManager.GetObject("String1", resourceCulture);
            return ((string)(obj));
        }
    }Instead of using GetString. I think I can sidestep this instance of the problem by making StringResource inherit from LiveObjectResource so it falls into that codepath, but I don't have a clear picture what to do about streams.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this different than what it generated before?  Calling GetObject instead of GetString is a little concerning as GetString is a separate codepath.  I haven't dug into the details to understand concretely if there's functional significance, but this difference is concerning enough that I bet its noticeable.  /cc @tarekms
GetStream is less concerning as this just builds on top of GetObject I don't see this as a big problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. In the old days and with my latest push 06ce897df, it generates:
    /// <summary>
    ///   Looks up a localized string similar to Value1.
    /// </summary>
    internal static string String1 {
        get {
            return ResourceManager.GetString("String1", resourceCulture);
        }
    }If string is the more worrisome case, I think it's fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I took a closer look at GetStream and found that it does do something special.
https://github.com/dotnet/corefx/blob/46d3f57a9bba6c79ee494996d465e33fd8effa32/src/Common/src/CoreLib/System/Resources/ResourceManager.cs#L753-L760
It does just call GetObject but it doesn't wrap the underlying UnmanagedMemoryStream.
Contrast this to calling GetObject and casting, and it will wrap the UMS and prevent access to the backing pointer:
https://github.com/dotnet/corefx/blob/46d3f57a9bba6c79ee494996d465e33fd8effa32/src/Common/src/CoreLib/System/Resources/ResourceManager.cs#L703
This would break the scenario where someone is down-casting back to UMS to get the pointer to an embedded stream. So I think we do need to preserve calls to GetStream too if we can.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ericstj Would you be comfortable with plumbing the assembly qualified type name down to here and doing matches on that?
IIUC I'd need to compare to either the full or core identities, right? so:
string:
System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System.String, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
MemoryStream:
System.IO.MemoryStream, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System.IO.MemoryStream, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
UMS:
System.IO.UnmanagedMemoryStream, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System.IO.UnmanagedMemoryStream, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
And there won't be a way to do the chain-up-to-a-public-type dance at all.
        
          
                src/Tasks/GenerateResource.cs
              
                Outdated
          
        
      There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
further down it's using StringComparer.InvariantCultureIgnore case. Should they use the same comparer, given both are resource ID's?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think that makes sense, but I'm tempted to leave this alone since it's all preexisting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably use OrdinalIgnoreCase throughout.
| With my latest push, this is now entangled with and a superset of #4607. I needed some of the constants and functions from there here and vice versa. If you'd like to diff unique changes in this PR: rainersigwald/msbuild@fileref-special-cases...StronglyTypedResourceBuilder-ResXDataNode | 
This is what the field always represented, but that wasn't obvious.
Helps provide more guarantees about the value types extracted from these members. Also deleted an unused overload of Create.
This adds `TypeFullName` to `IResource` and consumes it when emitting code of specific types in StronglyTypedResourceBuilder. There are two special cases: * Unknown types (because the type is known only after deserializing a BinaryFormatter blob): just use `object`. This is less good than the old Full Framework implementation, which did the deserialize, got live objects, and then could look at types. * Live objects: get the actual type from the object, use that. This is how it always worked before on full framework, so it preserves behavior in that case.
76c1377    to
    6b8d22f      
    Compare
  
    | Accidentally closed due to miswording in another PR :( | 
        
          
                src/Tasks/GenerateResource.cs
              
                Outdated
          
        
      There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will catch a lot more than just ConfigurationException, most exceptions derive from SystemException. If anything I'd say just catch all exceptions and log an error and avoid the ifdef but that's cleanup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please please please do not log just Exception.Message and some generic text for anything but a specific exception. This just makes bugs harder to diagnose
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even when this is a catch around a single method call? Nick: for background CodeDom is throwing a private ConfigurationErrorsException https://github.com/dotnet/corefx/blob/2fac89f63c644ab81d936d62c91e6003302e718d/src/System.CodeDom/src/System/CodeDom/Compiler/CodeDomProvider.cs#L215-L222. I suspect @stephentoub did this to avoid the dependency on ConfigurationManger. Catching SystemException is nearly as bad as catching Exception, so your feedback should apply the same to the current PR. You could check the type name (hack) and rethrow, maybe file an issue for us to push ConfigurationErrorsException down and throw the public version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, my feedback was to both the diff as is and the suggestion of changing to Exception.
It's better if it's a single method call, but then we'll end up with a bug in that method that is obscured. More hair falls out every time a bloody program tells me "object reference is not set to an instance of an object" like I did something wrong.
Can we do catch (Exception ex) when (ex.GetType().Name == "ConfigurationErrorsException")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, and I'll file a corefx bug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dotnet/corefx#40456 filed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect @stephentoub did this to avoid the dependency on ConfigurationManger.
Yes, System.Configuration didn't exist in corefx at the time. It wasn't brought in until several releases later.
6b8d22f    to
    cb8a512      
    Compare
  
    There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approve modulo the exception comments.
In .NET Core, CreateProvider throws a private exception derived from SystemException instead of one derived from ConfigurationException. This simplified catch removes duplication in the catch blocks.
This allows STRB to generate the right specialized accessor GetString() instead of returning (string)GetObject(...).
c13516d    to
    5dfbd71      
    Compare
  
    | ValueAsString = valueAsString; | ||
| } | ||
|  | ||
| internal ResourceData(string typeName, string typeAssemblyQualifiedName) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When reading the code that calls the constructors, it's confusing because the second argument means something entirely different depending on whether the first argument is a Type or just a string type name.  I'm not sure there's a great way to make this clearer though.
| type = typeof(UnmanagedMemoryStream); | ||
| } | ||
|  | ||
| // Ensure type is internalally visible. This is necessary to ensure | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: internalally (also further down on comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Existing, but I'd love to clean it up for 16.4.
|  | ||
| internal static bool IsMemoryStream(string fileRefType) | ||
| { | ||
| return fileRefType.Equals(MemoryStreamTypeNameDesktopFramework, StringComparison.Ordinal); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this only work when building on the Desktop framework?  Or should MemoryStreamTypeNameDesktopFramework be renamed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is very confusing. This codepath works on both core and desktop, but it only supports type names serialized into the resx that match the desktop names. Other tooling is responsible for ensuring that those names are used at resx-writing time. Some further discussion at #4607 (comment)
This regressed (#4582) with #4420, because the (untyped, .NET 1.0)
Dictionarypassed toCreatenever containedResXDataNodes any more, but the new typeLiveObjectResourcethat holds live deserialized objects in memory.This fixes only the full-framework scenarios that use$(GenerateResourceUsePreserializedResources)=false. When building for .NET Core with the default settings, or using .NET Core to build,StronglyTypedResourceBuilderstill doesn't work (#2272).Description
Enable
StronglyTypedResourceBuilderwhen running on .NET Core and ensure that projects built using the .NET Core codepath get code generated using the expected types for resources. Fixes #4588, fixes #2272.Customer Impact
Projects that used
ResXFileCodeGeneratorcould fail to compile due to generated code losing type information. This impacted WPF.Projects affected by either problem would have to workaround by:
MSBuild.exe, notdotnet buildRegression
Regression from .NET Framework functionality; bugs in features new to crossplatform .NET Core MSBuild.
Risk
Low--impacts only the new resource codepaths.