I am new to using Roslyn and the truth is that I am finding it quite interesting, but when using lambda expressions I am blocked. I would like to generate a property with getters and setters with lambda, so it looks like this:
private string uiDescription;
private string uiDescription;
public override string UiDescription {
get => uiDescription ?? Name;
set => uiDescription = value;
}
Would it be possible? Thanks in advance
I already found it, so I'll leave it here in case anyone is interested. Surely it can be done better:
var variableDeclarationDescription = SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName("string"))
.AddVariables(SyntaxFactory.VariableDeclarator("uiDescription"));
var fieldDeclarationDescription = SyntaxFactory.FieldDeclaration(variableDeclarationDescription)
.AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword));
var descriptionBody = variableDeclarationDescription.Variables.ToString()+ " ?? Name;"
var resourceGet = SyntaxFactory.IdentifierName(descriptionBody);
descriptionBody = variableDeclarationDescription.Variables.ToString() + " = value ;";
var resourceSet = SyntaxFactory.IdentifierName(descriptionBody);
var propertyGetLambda = SyntaxFactory.ArrowExpressionClause(resourceGet);
var propertySetLambda = SyntaxFactory.ArrowExpressionClause(resourceSet);
var propertyDeclarationDescription = SyntaxFactory.PropertyDeclaration(SyntaxFactory.ParseTypeName("string"), "UiDescription")
.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.OverrideKeyword))
.AddAccessorListAccessors(
SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).
WithExpressionBody(propertyGetLambda),
SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).
WithExpressionBody(propertySetLambda)
);
I hope it can serve someone in the future. Thanks
Related
using the Roslyn SyntaxTree API, I'd like to replace the literal value "UV254" with a new value.
Example:
public class Analog
{
public const string Value = "UV254";
}
After update
public class Analog
{
public const string Value = "UV220";
}
I came up with the below solution but I suspect this could be simplified:
string sourceCode = "public class Analog { public const string Value = \"UV254\"; public const string Description = \"A Description\";}";
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(sourceCode);
CompilationUnitSyntax syntaxRoot = syntaxTree.GetCompilationUnitRoot();
LiteralExpressionSyntax afterLiteralExpressionSyntax = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal("UV220"));
LiteralExpressionSyntax beforeLiteralExpressionSyntax = null;
foreach (VariableDeclarationSyntax variableDeclarationSyntax in syntaxRoot.DescendantNodes().OfType<VariableDeclarationSyntax>())
{
foreach(VariableDeclaratorSyntax variableDeclaratorSyntax in variableDeclarationSyntax.Variables)
{
if(variableDeclaratorSyntax.Identifier.ValueText == "Value")
{
beforeLiteralExpressionSyntax = variableDeclaratorSyntax.DescendantNodes().OfType<LiteralExpressionSyntax>().Single();
break;
}
}
if(beforeLiteralExpressionSyntax != null)
{
break;
}
}
var newRoot = syntaxRoot.ReplaceNode(beforeLiteralExpressionSyntax, afterLiteralExpressionSyntax);
var fixedTree = newRoot.SyntaxTree.WithRootAndOptions(newRoot, syntaxTree.Options);
Can this be simplified?
Thanks for the help.
I think you can use some LINQ to shorten the determination of beforeLiteralExpressionSyntax. You could write the following instead:
LiteralExpressionSyntax beforeLiteralExpressionSyntax =
syntaxRoot.DescendantNodes().OfType<VariableDeclarationSyntax>()
.SelectMany(decl => decl.Variables)
.FirstOrDefault(declarator => declarator.Identifier.ValueText == "Value")
?.DescendantNodes().OfType<LiteralExpressionSyntax>()
.Single();
This will assign a null value to beforeLiteralExpressionSyntax if the field wasn't found. If you're sure the field will always be there, you could replace FirstOrDefault with First and replace the ?. with ..
Other than that, I don't think there is much you can do to simplify the code. My experience of working with Roslyn is that ultimately it is quite complicated to navigate through the syntax tree, pick out relevant bits of it and make changes to it, but I guess some of that is inevitable because it reflects the complexity of the C# language.
I am trying to learn to use DynamoDB with C#
I have Partial updates, Put, Delete working.
Conditional update works (If the Attributes is part of root object).
I have the following model created.
Person with a lot of attributes.
The following works:
Expression expr = new Expression();
expr.ExpressionStatement = "Age = :age";
expr.ExpressionAttributeValues[":age"] = 26;
UpdateItemOperationConfig config = new UpdateItemOperationConfig
{
ConditionalExpression = expr,
ReturnValues = ReturnValues.AllNewAttributes
};
Document updatedPerson2 = personCatalog.UpdateItem(doc, config);
But what if my condition was on the Pet Name?
I have tried a couple of approaches with no luck eg:
expr.ExpressionStatement = "Pet.Name = :name";
expr.ExpressionAttributeValues[":name"] = "Lilleper";
Hope someone can help :) Or just nudge me in the right direction.
Try externalizing the Name sub-attribute name to ExpressionAttributeNames.
Expression expr = new Expression();
expr.ExpressionStatement = "Pet.#name = :name";
expr.ExpressionAttributeNames["#name"] = "Name";
expr.ExpressionAttributeValues[":name"] = "Lilleper";
I know how to create a local variable inside a method, for example this:
LocalDeclarationStatement(VariableDeclaration(IdentifierName("MyClass"))
.WithVariables(SingletonSeparatedList(VariableDeclarator(Identifier("nameOfvariable"))
.WithInitializer(
EqualsValueClause(
ObjectCreationExpression(IdentifierName("MyClass")).WithArgumentList(arguments)
.WithNewKeyword(Token(SyntaxKind.NewKeyword)))))));
would give me:
MyClass nameOfvariable = new MyClass();
But say that I already created a field and now I simply want to initialize it (in a method, constructor or anything) like this:
nameOfVariable = new MyClass();
How do I do this? My guess it have to do with the VariableDeclerator but I can't find a way to get it right so I can add it to a list that contains StatementSyntaxes. I can change the VariableDecleration to "VariableDeclaration(IdentifierName(""))" too but that gives me an ugly extra space infront of the statement.
It seems like I struggle with some really basic stuff of Roslyn and I try to check http://roslynquoter.azurewebsites.net/ but that feels like the forced way to do it (feels like it create a lot more code than necessary).
Update: Should clarify that I know how to create method/constructors. I'm only looking for a way to initialize a field when I only have access to the field name and field type. So the only code I want to generate is this:
myField = new MyField();
Well you're almost there, you just need to create all that. This should do what you're interested in:
const string source = #"
using System;
class MyClass
{
void Method()
{
MyClass nameOfVariable;
}
}
";
var tree = CSharpSyntaxTree.ParseText(source);
var compilation = CSharpCompilation.Create("MyCompilation", new[] { tree }, new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) });
var semanticModel = compilation.GetSemanticModel(tree);
var root = tree.GetRoot();
var local = root.DescendantNodes().OfType<LocalDeclarationStatementSyntax>().First();
var declaration = local.Declaration;
var declarator = declaration.Variables.First();
var identifier = SyntaxFactory.IdentifierName("MyClass");
var objectCreationExpression = SyntaxFactory.ObjectCreationExpression(identifier, SyntaxFactory.ArgumentList(), null);
var equalsValueClause = SyntaxFactory.EqualsValueClause(objectCreationExpression);
var newDeclarator = declarator.WithInitializer(equalsValueClause).WithAdditionalAnnotations(Formatter.Annotation);
var newRoot = root.ReplaceNode(declarator, newDeclarator);
var formattedRoot = Formatter.Format(newRoot, Formatter.Annotation, new AdhocWorkspace());
Console.WriteLine(formattedRoot.GetText());
Console.Read();
Some explanation: you create a new identifier MyClass which will be used in your ObjectCreationExpression. Then you wrap all that in an EqualsValueClause and you set that as an initializer to your declarator. We also add the Formatter annotation to this node so we can format it later and don't end up with whitespace issues.
All that's left then is replacing the node in your original tree, formatting it and you're done:
-------------------------------------------------------------------------------
If you instead mean that you want to put the assignment on its own separately from the declaration then you have to create a new AssignmentExpression and wrap it inside a ExpressionStatement. Typically expressions and statements are distinct concepts but this ExpressionStatement allows us to treat an expression as a statement which is important because a method's body only accepts statements.
In code, it looks like this:
internal static void Execute()
{
const string source = #"
using System;
class MyClass
{
void Method()
{
MyClass nameOfVariable, another;
}
}
";
var tree = CSharpSyntaxTree.ParseText(source);
var compilation = CSharpCompilation.Create("MyCompilation", new[] { tree }, new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) });
var semanticModel = compilation.GetSemanticModel(tree);
var root = tree.GetRoot();
var local = root.DescendantNodes().OfType<LocalDeclarationStatementSyntax>().First();
var method = local.Ancestors().OfType<MethodDeclarationSyntax>().First();
var variableIdentifier = SyntaxFactory.IdentifierName("nameOfVariable");
var classIdentifier = SyntaxFactory.IdentifierName("MyClass");
var objectCreationExpression = SyntaxFactory.ObjectCreationExpression(classIdentifier, SyntaxFactory.ArgumentList(), null);
var assignment = SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, variableIdentifier, objectCreationExpression);
var expressionStatement = SyntaxFactory.ExpressionStatement(assignment).WithAdditionalAnnotations(Formatter.Annotation);
var newMethod = method.AddBodyStatements(expressionStatement);
var newRoot = root.ReplaceNode(method.Body, newMethod.Body);
var formattedRoot = Formatter.Format(newRoot, Formatter.Annotation, new AdhocWorkspace());
Console.WriteLine(formattedRoot.GetText());
Console.Read();
}
Result:
After some more trying and looking I found the answer. There is something called "AssignmentExpression" that you can use.
Here is an example how to use it:
ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, IdentifierName("myField"),
ObjectCreationExpression(IdentifierName("MyClass")).WithArgumentList(arguments)
.WithNewKeyword(Token(SyntaxKind.NewKeyword))));
This would give you:
myField = new Myclass();
So now it's easy to seperate creation and assignment/initialization to two different statements.
Note that I'm using "using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;" so I don't have to write SyntaxFactory all the time.
Or you can goto "http://roslynquoter.azurewebsites.net/" and paste your code in the small little textbox and click "Get Roslyn API calls to generate this code".
(I can generate the code you posted above, but it is kinda long. so i use a simple example.
For example, let's say you paste "DateTime mydate2 = new DateTime()", the tool will generate the following code :-
LocalDeclarationStatement(
VariableDeclaration(
IdentifierName("DateTime"))
.WithVariables(
SingletonSeparatedList<VariableDeclaratorSyntax>(
VariableDeclarator(
Identifier("mydate2"))
.WithInitializer(
EqualsValueClause(
ObjectCreationExpression(
IdentifierName("DateTime"))
.WithArgumentList(
ArgumentList())))))).WithSemicolonToken(
MissingToken(SyntaxKind.SemicolonToken)).NormalizeWhitespace()
Then you just have to fix up the code using SyntaxFactory, for example :-
var myDeclaratyion = SyntaxFactory.LocalDeclarationStatement(
SyntaxFactory.VariableDeclaration(
SyntaxFactory.IdentifierName("DateTime")).
WithVariables(
SyntaxFactory.SingletonSeparatedList<VariableDeclaratorSyntax>(
SyntaxFactory.VariableDeclarator(
SyntaxFactory.Identifier("mydate2")).
WithInitializer(
SyntaxFactory.EqualsValueClause(
SyntaxFactory.ObjectCreationExpression(
SyntaxFactory.IdentifierName("DateTime"))
.WithArgumentList(
SyntaxFactory.ArgumentList())))))).WithSemicolonToken(SyntaxFactory.MissingToken(SyntaxKind.SemicolonToken)).NormalizeWhitespace();
Supposing I have the following anonymous type
var g = records.Select(r => new
{
Id = r.CardholderNo,
TimeIn = r.ArriveTime,
TimeOut = r.LeaveTime,
});
Is it possible to do something like the following:
var g = records.Select(r => new
{
Id = r.CardholderNo,
if (condition)
{
TimeIn = r.ArriveTime;
},
TimeOut = r.LeaveTime,
//many more properties that I'd like to be dependant on conditions.
});
How can I achieve an anonymous type based on conditions?
You can do this by using the ternary operator: ?:
The syntax is like this:
TimeIn = condition ? r.ArriveTime : (DateTime?)null // Or DateTime.Min or whatever value you want to use as default
UPDATE
After thinking about your problem for a couple of minutes I came up with the following code that you should never ever use ;)
using System;
class Program
{
static void Main(string[] args)
{
DateTime dt = DateTime.Now;
bool condition = true;
dynamic result = condition ?
(object)new
{
id = 1,
prop = dt
}
:
(object)new
{
id = 2,
};
Console.WriteLine(result.id);
if (condition) Console.WriteLine(result.prop);
}
}
This code should never be used in production because of it's terrible readability and it's really error prone. However, as a learning example of what's possible with the language it's quite nice.
Not directly using an if statement, but you could do it using the ternary operator (assuming TimeIn is of type DateTime):
var g = records.Select(r => new
{
Id = r.CardholderNo,
TimeIn = condition ? r.ArriveTime : (DateTime?)null;
TimeOut = r.LeaveTime
});
Note this will make the property always appear in your Annonymous Type. If this isn't the desired behavior, then you can't do it this way.
I would suggest thinking about the readability of your code and not only about "how can i shorten these few lines so it looks neat".
No. Anonymous types are just like any other type. It has a fixed list of properties. You can't dynamically add or remove properties.
I suggest to either set the property to null, like in the other answers, or use a Dictionary where you add the relevant properties and their values.
If you really need an if (or any another statement) in your Anonymous Type creation, you can try this not-so-pretty solution:
var g = records.Select(r => new
{
Id = r.CardholderNo,
TimeIn = new Func<DateTime?, DateTime?>(x =>
{
if (...)
return x;
else
return null;
}).Invoke(r.ArriveTime),
TimeOut = r.LeaveTime,
});
I have the following code and it is working fine. However I am new to using "IEnumerable code" and it would seem obvious that it could be done better.
Basically I want all Region nodes in the XML and then the data I want to output in my Asp:repeater is nested quite deeply in the XML, but the 4 fields are all at the same level.
var xDoc = xmlDoc.ToXDocument();
var jobs = xDoc.Descendants("Region")
.Select(x => new {
jobName = x.Element("Location").Element("Department").Element("Brand").Element("Jobs").Element("Job").Element("JobName").Value,
jobType = x.Element("Location").Element("Department").Element("Brand").Element("Jobs").Element("Job").Element("JobType").Value,
jobURL = x.Element("Location").Element("Department").Element("Brand").Element("Jobs").Element("Job").Element("URL").Value,
jobClose = x.Element("Location").Element("Department").Element("Brand").Element("Jobs").Element("Job").Element("JobCLDate").Value
}
);
if (jobs.Count() > 0)
{
careersListing.DataSource = jobs;
careersListing.DataBind();
careersListing.Visible = true;
}
I would be very grateful of any feedback with respect to making it more succinct
Thanks
Nigel
You're right; this can be inefficient.
You can simplify it like this:
var jobs = from x in xDoc.Descendants("Region")
let job = x.Element("Location").Element("Department").Element("Brand").Element("Jobs").Element("Job")
select new {
jobName = job.Element("JobName").Value,
...
};
If you prefer to use method call syntax, you can pass a statement lambda expression that declares a temporary variable.
If there is just one Job element for each region (which seems to be the case), why not just query for it directly?
var jobs = xDoc.Descendants("Job")
.Select(x => new {
jobName = x.Element("JobName").Value,
jobType = x.Element("JobType").Value,
jobURL = x.Element("URL").Value,
jobClose = x.Element("JobCLDate").Value
}
);
Another minor optimization: Use Any() instead of Count():
if (jobs.Any())
{
careersListing.DataSource = jobs;
careersListing.DataBind();
careersListing.Visible = true;
}
if (jobs.Count() > 0) can be rewritten as if (jobs.Any()).