Adding class members into specific locations using Roslyn? - c#

I'm using ClassDeclarationSyntax.AddMembers() to add methods to the class. The method appears in the class, but I would like to know how to add the method to a specific place. As of now, they are added to the #if directive at the end of the class.
Roslyn version: 4.4.0
Running the code:
var tree = CSharpSyntaxTree.ParseText(#"
namespace Test
{
public class A
{
#if !SILVERLIGHT
public int someField;
#endif
}
}");
var classNode = tree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().First();
var previousWhiteSpacesToken =
SyntaxFactory.Token(SyntaxTriviaList.Empty, SyntaxKind.None, SyntaxTriviaList.Empty);
var method = SyntaxFactory.MethodDeclaration( //
SyntaxFactory.PredefinedType( //
SyntaxFactory.Token(SyntaxKind.IntKeyword)), "CalculateSize") //
.WithModifiers(SyntaxTokenList.Create(previousWhiteSpacesToken)
.Add(SyntaxFactory.Token(SyntaxKind.PublicKeyword))) //
.WithBody(SyntaxFactory.Block()).NormalizeWhitespace();
var newClassNode = classNode.AddMembers(method).NormalizeWhitespace();
Console.WriteLine(newClassNode.ToString());
This result will be obtained:
public class A
{
#if !SILVERLIGHT
public int someField;
public int CalculateSize()
{
}
#endif
}
I know that there is a way to do this, but this result can only guarantee correctness:
public class A
{
public int CalculateSize()
{
}
#if !SILVERLIGHT
public int someField;
#endif
}
I expected this result:
public class A
{
#if !SILVERLIGHT
public int someField;
#endif
public int CalculateSize()
{
}
}

The problem here is that #endif is the leading trivia of } that closes the class declaration. So, when you just add a member, the #endif remains as the leading trivia of }.
What you need to do is to move the leading trivia of } to be the leading trivia of the new method you are adding.
Here is the modified version of your code:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
var tree = CSharpSyntaxTree.ParseText(#"
namespace Test
{
public class A
{
#if !SILVERLIGHT
public int someField;
#endif
}
}");
var classNode = tree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().First();
var previousWhiteSpacesToken =
SyntaxFactory.Token(SyntaxTriviaList.Empty, SyntaxKind.None, SyntaxTriviaList.Empty);
var method = SyntaxFactory.MethodDeclaration( //
SyntaxFactory.PredefinedType( //
SyntaxFactory.Token(SyntaxKind.IntKeyword)), "CalculateSize") //
.WithModifiers(SyntaxTokenList.Create(previousWhiteSpacesToken)
.Add(SyntaxFactory.Token(SyntaxKind.PublicKeyword))) //
.WithBody(SyntaxFactory.Block())
.WithLeadingTrivia(classNode.CloseBraceToken.LeadingTrivia).NormalizeWhitespace(); // copy '}' leading trivia to the new method.
// remove leading trivia from '}', they are moved previously to the new method.
var newClassNode = classNode.WithCloseBraceToken(classNode.CloseBraceToken.WithLeadingTrivia()).AddMembers(method).NormalizeWhitespace();
Console.WriteLine(newClassNode.ToString());
This prints the following (exactly your expectation):
public class A
{
#if !SILVERLIGHT
public int someField;
#endif
public int CalculateSize()
{
}
}

Related

Passing Nullable Int using reflection

i am using this code
//Rextester.Program.Main is the entry point for your code. Don't change it.
//Compiler version 4.0.30319.17929 for Microsoft (R) .NET Framework 4.5
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Reflection;
namespace Rextester
{
public class Program
{
public static void Main(string[] args)
{
new Program().DoTest();
}
public void DoTest()
{
var a = LoggingAdvice<asd>.Create(new a());
a.targetMethod("nadeem", 123, null);
}
}
public class LoggingAdvice<T> : DispatchProxy
{
private T _decorated;
protected override object Invoke(MethodInfo targetMethod, object[] args)
{
var result = targetMethod.Invoke(_decorated, args);
var resultTask = result as Task;
return result;
}
public static T Create(T decorated)
{
object proxy = Create<T, LoggingAdvice<T>>();
((LoggingAdvice<T>)proxy).SetParameters(decorated);
return (T)proxy;
}
private void SetParameters(T decorated)
{
if (decorated == null)
{
throw new ArgumentNullException(nameof(decorated));
}
_decorated = decorated;
}
}
public class asd
{
public asd()
{
}
public int targetMethod(string name, int number, int? count)
{
Console.WriteLine(name);
Console.WriteLine(number);
Console.WriteLine(count.HasValue ? count.Value.ToString() : "NULL");
return 1;
}
}
}
the most important thing about it, is this line of code
var result = targetMethod.Invoke(_decorated, args);
the target method has 3 parameters, one of them is nullable integer as the following
public int targetMethod(string name, int number, int? count)
the args that is getting passed to Invoke method has the following values as you can view it in the visual studio debugging mode:
["nadeem", 123, (null)]
the invoke is raising an exception that i cannot convert string to nullable integer.
how i can avoid this type of issue, note that i am not sure which function i am going to be invoked, i would only know it on runtime.

Proper Disposal of Unamanged Struct

Hello this question is a continuation of this question. Provided below is a short version of the code that is loading the data into the structure. The HDF5DotNet library is found here using the 32 bit version with vs2015. My question is, will MyStruct leak memory when SomeCallerClass call HDF5GetSubGroups?
using HDF5DotNet;
namespace CommonClass
{
public class HDF5FileReader
{
public static List<string> HDF5GetSubGroups(string filePath)
{
var returnList = new List<string>();
//... some HDF5 instantiating -- commented out for brevity
var myStruct = new MyStruct[numberOfThingsToRead];
H5A.read(someAttribute, someAttributeType, new H5Array<MyStruct>(myStruct));
string myStructVariableString = IntPtrToString(myStruct[0].intPtr);
returnList.Add(myStructVariableString);
//... closing some HDF5 instantiating -- commented out for brevity
return returnList
}
private string IntPtrToString(IntPtr ipp)
{
string s = Marshal.PtrToStringAnsi(ipp)
//need to free ipp?
return s;
}
}
public struct MyStruct
{
public Int int1;
public IntPtr intPtr;
}
}
namespace CallerClass
{
public class SomeCallerClass()
{
string someFilePath = "Path\To\HDF5File.h5"
var stringList = HDF5FileReader.HDF5GetSubGroups(someFilePath)
//Do Something with StringList -- add to observablecollection
}
public class UnloadingSomeCallerClass()
{
//Clear the observablecollection
}
}

C# float or double choosing pre-compilation

We recently realized we want to replace our all project from double to float while still saving the option to use double sometimes.
The question is: what is the best practice to do it?
We thought aliasing is the right thing to do but we've found global aliasing is not supported in C#.
Here is an example for the aliasing we've done:
#if USE_FLOAT
using mFloatType = System.Single;
using mComplexType = CenterSpace.NMath.Core.FloatComplex;
using mComplexVector = CenterSpace.NMath.Core.FloatComplexVector;
using mComplexMatrix = CenterSpace.NMath.Core.FloatComplexMatrix;
using mHermitianMatrix = CenterSpace.NMath.Matrix.FloatHermitianMatrix;
#else
using mFloatType = System.Double;
using mComplexType = CenterSpace.NMath.Core.DoubleComplex;
using mComplexVector = CenterSpace.NMath.Core.DoubleComplexVector;
using mComplexMatrix = CenterSpace.NMath.Core.DoubleComplexMatrix;
using mHermitianMatrix = CenterSpace.NMath.Matrix.DoubleHermitianMatrix;
#endif
While USE_FLOAT is a define symbol.
However, putting this piece of code in every file in the project (more than 500 files) seems totally wrong, especially in object oriented programming.
Any ideas how to do this transition?
If it helps, we are using monoedevelop 6.3 with Mono 4.0.
Thanks.
Since you are dealing with sealed types, you could go with a type factory so that #if/#else/#endif block is only in one file.
Just an example, there are a few ways to do this:
using System;
#region USING_FLOAT
#if USE_FLOAT
using mFloatType = System.Single;
using mComplexType = CenterSpace.NMath.Core.FloatComplex;
using mComplexVector = CenterSpace.NMath.Core.FloatComplexVector;
using mComplexMatrix = CenterSpace.NMath.Core.FloatComplexMatrix;
using mHermitianMatrix = CenterSpace.NMath.Matrix.FloatHermitianMatrix;
#else
using mFloatType = System.Double;
using mComplexType = CenterSpace.NMath.Core.DoubleComplex;
using mComplexVector = CenterSpace.NMath.Core.DoubleComplexVector;
using mComplexMatrix = CenterSpace.NMath.Core.DoubleComplexMatrix;
using mHermitianMatrix = CenterSpace.NMath.Matrix.DoubleHermitianMatrix;
#endif
#endregion
namespace TypeFactory
{
public static class CenterLineFactory
{
public static mFloatType Double { get { return new mFloatType(); } }
public static mComplexType ComplexType { get { return new mComplexType(); } }
~~~ etc ~~~
}
}
Usage:
var aFloat = CenterLineFactory.Double;
var aComplexType = CenterLineFactory.ComplexType;
But due to the fact this is an afterthought on how your double/float choice is made, it would require all 500 of those files need updated anyway on how you are creating these 5 types...
Personally:
I would use Microsoft's CodeFormatter (https://github.com/dotnet/codeformatter) to bulk convert all your files at once and insert your #if USE_FLOAT/#else/#endif block within a #region USING_FLOAT/#endregion.
I normally never use regions, but would for this. This way you could auto-collapse the USING_FLOAT region and remove it from creating a visual code smell.
One approach could be to use composition instead of inheritance and create a SingleOrDouble struct as below which imitates the behavior of Single or Double based on the build symbol.
You will still need to update all 500 files but it will be a simple find all System.Single and replace with SingleOrDouble
#if USE_FLOAT
public struct SingleOrDouble : IComparable
, IComparable<Single>, IEquatable<Single> {
public Single Value;
private SingleOrDouble(float f) {
Value = f;
}
public static implicit operator float(SingleOrDouble s) {
return s.Value;
}
public static implicit operator SingleOrDouble(float f) {
return new SingleOrDouble(f);
}
public int CompareTo(float other) {
return Value.CompareTo(other);
}
public bool Equals(float other) {
return Value.Equals(other);
}
public static bool IsInfinity(float f) {
return Single.IsInfinity(f);
}
#else
public struct SingleOrDouble : IComparable
, IComparable<Double>, IEquatable<Double> {
public Double Value { get; set; }
private SingleOrDouble(double d) {
Value = d;
}
public static implicit operator double(SingleOrDouble d) {
return d.Value;
}
public static implicit operator SingleOrDouble(double d) {
return new SingleOrDouble(d);
}
public int CompareTo(double other) {
return Value.CompareTo(other);
}
public bool Equals(double other) {
return Value.Equals(other);
}
#endif
public int CompareTo(object obj) {
return Value.CompareTo(obj);
}
public TypeCode GetTypeCode() {
return Value.GetTypeCode();
}
public static bool IsInfinity(double d) {
return Double.IsInfinity(d);
}
}
Please note that above class is not complete but you get the idea.
The class variable can be initialised as below
SingleOrDouble s = 100.5;
which is no different from intializing a normal double as below
double d = 100.5;
Hope this helps

Removing and Adding content with Code Fix Provider

I had that previous question which was intended to resolved the state of a local variable / parameter. It works fine, I tweak it a little bit and it looks like this now :
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;
using System.Linq;
namespace RefactoringEssentials.CSharp.Diagnostics
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class LocalVariableNotUsedAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor descriptor = new DiagnosticDescriptor(
CSharpDiagnosticIDs.LocalVariableNotUsedAnalyzerID,
GettextCatalog.GetString("Local variable is never used"),
GettextCatalog.GetString("Local variable is never used"),
DiagnosticAnalyzerCategories.RedundanciesInDeclarations,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
helpLinkUri: HelpLink.CreateFor(CSharpDiagnosticIDs.LocalVariableNotUsedAnalyzerID)
);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(descriptor);
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(
(nodeContext) =>
{
Diagnostic diagnostic;
if (TryGetDiagnostic(nodeContext, out diagnostic))
{
nodeContext.ReportDiagnostic(diagnostic);
}
},
SyntaxKind.LocalDeclarationStatement
);
}
private static bool TryGetDiagnostic(SyntaxNodeAnalysisContext nodeContext, out Diagnostic diagnostic)
{
diagnostic = default(Diagnostic);
if (nodeContext.IsFromGeneratedCode())
return false;
var localDeclarationUnused = nodeContext.Node as LocalDeclarationStatementSyntax;
var body = localDeclarationUnused?.Parent as BlockSyntax;
if (body == null)
return false;
var dataFlow = nodeContext.SemanticModel.AnalyzeDataFlow(body);
var variablesDeclared = dataFlow.VariablesDeclared;
var variablesRead = dataFlow.ReadInside.Union(dataFlow.ReadOutside);
var unused = variablesDeclared.Except(variablesRead).ToArray();
if (unused == null)
return false;
if (localDeclarationUnused.Declaration == null || !localDeclarationUnused.Declaration.Variables.Any())
return false;
var localDeclarationSymbol = nodeContext.SemanticModel.GetDeclaredSymbol(localDeclarationUnused.Declaration.Variables.FirstOrDefault());
if (unused.Any())
{
if (unused.Contains(localDeclarationSymbol))
{
diagnostic = Diagnostic.Create(descriptor, localDeclarationUnused.Declaration.GetLocation());
return true;
}
}
return false;
}
}
}
I have build a code fix provider which is working around 40% of the time, when checking the success rate of the NUnit test. Even though I know the code is OK, there seem to be some error on my machine that only arrives when the code fix is being run. I know this because I can debug the analyzer for the tests and each one are fine.
When the code fix provider is being run, I have this error that I can't shake for some reason : "System.ArgumentNullException : Value cannot be null.
Parameter name : declaration"
I have tried debugging the code fix provider, but nothing showed me where that declaration parameter could be located.
Moreover, I am used with code fix providers that need to either replace and remove nodes. But I'm not used to fix an error and add content and I'm wondering how to do such a thing.
Here's my code fix provider which does not take care of adding information at the moment :
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CodeFixes;
using System.Threading.Tasks;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace RefactoringEssentials.CSharp.Diagnostics
{
[ExportCodeFixProvider(LanguageNames.CSharp), System.Composition.Shared]
public class LocalVariableNotUsedCodeFixProvider : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds
{
get
{
return ImmutableArray.Create(CSharpDiagnosticIDs.LocalVariableNotUsedAnalyzerID);
}
}
public override FixAllProvider GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}
public async override Task RegisterCodeFixesAsync(CodeFixContext context)
{
var document = context.Document;
var cancellationToken = context.CancellationToken;
var span = context.Span;
var diagnostics = context.Diagnostics;
var root = await document.GetSyntaxRootAsync(cancellationToken);
var diagnostic = diagnostics.First();
var node = root.FindNode(context.Span);
if (node == null)
return;
var newRoot = root.RemoveNode(node, SyntaxRemoveOptions.KeepNoTrivia);
context.RegisterCodeFix(CodeActionFactory.Create(node.Span, diagnostic.Severity, "Remove unused local variable", document.WithSyntaxRoot(newRoot)), diagnostic);
}
}
}
and here are the tests that I'm using to make sure that the fix is ok. The last two are running just fine :-)
using NUnit.Framework;
using RefactoringEssentials.CSharp.Diagnostics;
namespace RefactoringEssentials.Tests.CSharp.Diagnostics
{
[TestFixture]
public class LocalVariableNotUsedTests : CSharpDiagnosticTestBase
{
[Test]
public void TestUnusedVariable()
{
var input = #"
class TestClass {
void TestMethod ()
{
$int i$;
}
}";
var output = #"
class TestClass {
void TestMethod ()
{
}
}";
Analyze<LocalVariableNotUsedAnalyzer>(input, output);
}
[Test]
public void TestUnusedVariable2()
{
var input2 = #"
class TestClass {
void TestMethod ()
{
$int i, j$;
j = 1;
}
}";
var output2 = #"
class TestClass {
void TestMethod ()
{
int j;
j = 1;
}
}";
Analyze<LocalVariableNotUsedAnalyzer>(input2, output2);
}
[Test]
public void TestUsedVariable()
{
var input1 = #"
class TestClass {
void TestMethod ()
{
$int i = 0$;
}
}";
var input2 = #"
class TestClass {
void TestMethod ()
{
int i;
i = 0;
}
}";
Analyze<LocalVariableNotUsedAnalyzer>(input1);
Analyze<LocalVariableNotUsedAnalyzer>(input2);
}
[Test]
public void TestUnusedForeachVariable()
{
var input = #"
class TestClass {
void TestMethod ()
{
var array = new int[10];
foreach (var i in array) {
}
}
}";
Analyze<LocalVariableNotUsedAnalyzer>(input);
}
[Test]
public void TestUsedForeachVariable()
{
var input = #"
class TestClass {
void TestMethod ()
{
var array = new int[10];
int j = 0;
foreach (var i in array) {
j += i;
}
}
}";
Analyze<LocalVariableNotUsedAnalyzer>(input);
}
}
}
Is there is anything that is not clear, I will make sure to update my thread appropriately.

XML Serialisation question

I'm trying to teach-myself how to serialize/deserialize to/from XML using the attribute-based serializer. I've put the below code together for testing purposes but it seems that I might have missed a point or two. Can anyone assist me and tell me what to do to have the whole damn thing work properly? The code below should compile and run just fine but will throw an exception - something is probably wrong with my attributes.
What did I miss?
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.IO;
namespace XMLSerialisation_test
{
class Program
{
static void Main(string[] args)
{
World my_world = new World(new Point(20, 30));
for (int i = 0; i < 10; i++)
{
string property = String.Format("Property no.{0}", i);
my_world.PushWorldObject(new MyObject(new Point(i, i), property));
}
DataContractSerializer world_serializer = new DataContractSerializer(typeof(World));
try
{
using (Stream s = File.Create("output.xml"))
world_serializer.WriteObject(s, my_world);
}
catch (Exception e)
{
Console.WriteLine("Exception occured : {0}", e.Message);
}
}
}
}
WorldObject.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
namespace XMLSerialisation_test
{
[DataContract]
public struct Point // couldn't find the pre-defined Point for some reason
{
public Point(double x, double y)
{ X = x; Y = y; }
[DataMember]
public double X;
[DataMember]
public double Y;
}
[DataContract]
public abstract class WorldObject
{
public WorldObject() : this(0.0, 0.0)
{}
public WorldObject(Point loc)
{ m_location = loc; }
public WorldObject(double x, double y)
{
m_location.X = x;
m_location.Y = y;
}
[DataMember]
public Point Location
{
get { return m_location; }
set { m_location = value; }
}
protected Point m_location;
}
[DataContract]
public class MyObject : WorldObject
{
public MyObject(string prop)
: base(0.0, 0.0)
{ m_property = prop; }
public MyObject(Point p, string prop)
: base(p)
{ m_property = prop; }
[DataMember]
public string Property
{
get{ return m_property; }
set{ m_property = value; }
}
private string m_property;
}
}
World.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Xml.Serialization;
namespace XMLSerialisation_test
{
[DataContract]
class World
{
public World() : this(new Point(10, 10))
{ }
public World(Point size)
{ m_world_size = size; }
public bool PushWorldObject(WorldObject o)
{
try
{
WorldObjects.Add(o);
return true;
}
catch(Exception e)
{
Console.WriteLine("Exception occured : {0}", e.Message);
return false; }
}
#region Accessors
[DataMember]
public List<WorldObject> WorldObjects
{
get { return m_world_objects; }
set { m_world_objects = value; }
}
[DataMember]
public Point WorldSize
{
get { return m_world_size; }
private set { m_world_size = value; }
}
#endregion
#region Fields
private List<WorldObject> m_world_objects = new List<WorldObject>();
private Point m_world_size;
#endregion
}
}
Try this:
DataContractSerializer world_serializer = new DataContractSerializer(typeof(World), new List<Type> { typeof(MyObject) });
The problem is that PushWorldObject takes type WorldObject, but you are actually passing type MyObject. The serializer knows nothing about this type, so throws an exception. WorldObject is used within the World class, so this type is by known by default. However, MyObject is not used inside World - so you manually have to declare it as Known.
As an alternative, you can add the KnownType attribute like so:
[DataContract, KnownType(typeof(MyObject))]
class World
{
The biggest problem I see is the abstract WorldObject. You'll need to use a KnownType attribute there (or via config or method name- check MSDN for "Data Contract Known Types") to tell it about the known subtypes it might encounter (eg, MyObject).
Just to clarify: if you are serializing to xml for the purposes of having xml in a known format, then DataContractSerializer is a bad choice; you would do better to use XmlSerializer, which has more control over xml layout. You would use the [XmlElement] / [XmlAttribute] / [XmlType] / [XmlRoot] attributes instead of [DataMember] etc.
If you just want to use xml because it is text and stores the object, then DataContractSerializer is fine; and has other advantages, for example - not demanding a public parameterless constructor (which XmlSerializer does), and working with private members (which XmlSerializer doesn't).

Categories

Resources