FluentAssertions 6 ObjectGraph compare Enum to String - c#

With FluentAssertions 6 it seems you can longer verify if in a object graph if an Enum is equivalent to a string. Source: https://fluentassertions.com/upgradingtov6
enum MyEnum {
A,
B
}
class Source {
MyEnum Enum { get;set;}
}
class Expectation {
string Enum { get;set;}
}
var source = new Source() { Enum = MyEnum.A };
var expectation = new Expectation() {Enum = "A"};
//With V6 this assertion will fail but in V5 it will pass
expectation.Should().BeEquivalentTo(source, options => options.ComparingEnumsByName());
How can I assert the objects above with FluentAssertions? The behaviour I want is for the assertions to be made on the ToString representation of the enum.
As I side note, I get different behaviour when I swap the expectation with source. Shouldn't they be equivalent?

You can define a more relaxed equivalency step to handle string/enum comparisons.
class RelaxedEnumEquivalencyStep : IEquivalencyStep
{
public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, IEquivalencyValidator nestedValidator)
{
if (comparands.Subject is string subject && comparands.Expectation?.GetType().IsEnum == true)
{
AssertionScope.Current
.ForCondition(subject == comparands.Expectation.ToString())
.FailWith(() =>
{
decimal? subjectsUnderlyingValue = ExtractDecimal(comparands.Subject);
decimal? expectationsUnderlyingValue = ExtractDecimal(comparands.Expectation);
string subjectsName = GetDisplayNameForEnumComparison(comparands.Subject, subjectsUnderlyingValue);
string expectationName = GetDisplayNameForEnumComparison(comparands.Expectation, expectationsUnderlyingValue);
return new FailReason(
$"Expected {{context:string}} to be equivalent to {expectationName}{{reason}}, but found {subjectsName}.");
});
return EquivalencyResult.AssertionCompleted;
}
if (comparands.Subject?.GetType().IsEnum == true && comparands.Expectation is string expectation)
{
AssertionScope.Current
.ForCondition(comparands.Subject.ToString() == expectation)
.FailWith(() =>
{
decimal? subjectsUnderlyingValue = ExtractDecimal(comparands.Subject);
decimal? expectationsUnderlyingValue = ExtractDecimal(comparands.Expectation);
string subjectsName = GetDisplayNameForEnumComparison(comparands.Subject, subjectsUnderlyingValue);
string expectationName = GetDisplayNameForEnumComparison(comparands.Expectation, expectationsUnderlyingValue);
return new FailReason(
$"Expected {{context:enum}} to be equivalent to {expectationName}{{reason}}, but found {subjectsName}.");
});
return EquivalencyResult.AssertionCompleted;
}
return EquivalencyResult.ContinueWithNext;
}
private static string GetDisplayNameForEnumComparison(object o, decimal? v)
{
if (o is null)
{
return "<null>";
}
if (v is null)
{
return '\"' + o.ToString() + '\"';
}
string typePart = o.GetType().Name;
string namePart = o.ToString().Replace(", ", "|", StringComparison.Ordinal);
string valuePart = v.Value.ToString(CultureInfo.InvariantCulture);
return $"{typePart}.{namePart} {{{{value: {valuePart}}}}}";
}
private static decimal? ExtractDecimal(object o)
{
return o?.GetType().IsEnum == true ? Convert.ToDecimal(o, CultureInfo.InvariantCulture) : null;
}
}
If it's just for a single test
var source = new Source() { Enum = MyEnum.A };
var expectation = new Expectation() { Enum = "A" };
expectation.Should().BeEquivalentTo(source, options => options.Using(new RelaxedEnumEquivalencyStep()));
source.Should().BeEquivalentTo(expectation, options => options.Using(new RelaxedEnumEquivalencyStep()));
Or if you want this globally you can set this using AssertionOptions.AssertEquivalencyUsing.
For MSTest put it in AssemblyInitialize.
For Xunit, see the documentation or use this Module Initializer example.
AssertionOptions.AssertEquivalencyUsing(e => e.Using(new RelaxedEnumEquivalencyStep()));
var source = new Source() { Enum = MyEnum.A };
var expectation = new Expectation() { Enum = "A" };
expectation.Should().BeEquivalentTo(source);
source.Should().BeEquivalentTo(expectation);
For completeness here are examples of the failure message when MyEnum and string do not match.
Expected property root.Enum to be equivalent to "B", but found MyEnum.A {value: 0}.
Expected property source.Enum to be <null>, but found MyEnum.B {value: 1}.
Expected property root.Enum to be equivalent to MyEnum.A {value: 0}, but found "B".
Expected property expectation.Enum to be equivalent to MyEnum.B {value: 1}, but found <null>.

Your expectation defines the Enum property as being a string, and the options you provide are used to instruct FA how the members of the expectation should be compared to the subject. So in this case, ComparingEnumsByName doesn't do anything since the property involved is a string. What you could do instead is to use an anonymous type as the expectation.

Related

Get enum value as string

I have an enum like this:
enum myEnum
{
a = 0101,
b = 2002,
c = 0303
}
I try to get enum value with casting but the 0 at the begin of my enums was removed.
For example I tried this:
var val = (int)myEnum.a;
How can get enum value as string when we have 0 at the beginning of it?
You should rethink your design, but if you want to check your enum integers to a given string, you can use .ToString("0000") to get the string "0101" out of the integer 101.
First, enums are integers, since as their name says, they are enumerations and an enumeration, they are numbers, so enum is integer.
Secondly, you must bear in mind that zero is a null value, since the system is a 01 or 001 like 1, since (basic mathematics) a zero to the left is worthless, so this code is incorrect.
enum myEnum
{
a=0101,
b=2002,
c=0303,
}
The correct way is
enum myEnum
{
a = 0,
b = 1,
c = 2
}
Where the zero is alone, so the system sees it as an index
Now with this, you should only use one of the conversion processes of C#
string strOne = ((myEnum)0).ToString();
string strTwo = ((myEnum)1).ToString();
string strThree = ((myEnum)2).ToString();
Read the MSDN reference https://msdn.microsoft.com/en-us/library/16c1xs4z(v=vs.110).aspx
Enumeration values are always integers. If you need to associate a string with an enumeration value, you can use a dictionary:
enum myEnum { a, b, c }
Dictionary<myEnum, string> lookup = new Dictionary
{
{ a, "0101" },
{ b, "2002" },
{ c, "0303" }
};
To get the string associated with a particular value just use this:
var s = lookup[myEnum.a]; // s = 0101
Another common way to handle this sort of problem is simply to use constants.
class MyConstants
{
public const string a = "0101";
public const string b = "2002";
public const string c = "0303";
}
var s = MyConstants.a; // s = 0101
Try using formatting: you want 4 digits and that's why you can put d4 format string. In order to hide all these implmentation details (cast and formatting) let's write an extension method:
enum myEnum {
a = 0101,
b = 2002,
c = 0303
}
static class myEnumExtensions {
public static string ToReport(this myEnum value) {
return ((int)value).ToString("d4"); // 4 digits, i.e. "101" -> "0101"
}
}
...
myEnum test = myEnum.a;
Console.Write(test.ToReport());
If you always need a specific number of digits you could use string format to get the leading zeros:
var str = String.Format("{0:0000}", (int)myEnum.a);
Or, shorter:
var str = $"{(int) myEnum.a:D4}";
Alternative:
Use an attribute to add extra information to an enum
Attribute:
public class DescriptionAttribute : Attribute
{
public string Name { get; }
public DescriptionAttribute(string name)
{
Name = name;
}
}
Enum:
enum myEnum
{
[Description("0101")]
a = 101,
[Description("2002")]
b = 2002,
[Description("303")]
c = 303
}
Extension Method:
public static string GetDescription(this myEnum e)
{
var fieldInfo = e.GetType().GetField(e.ToString());
var attribute = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false).FirstOrDefault() as DescriptionAttribute;
return attribute.Name;
}
Usage:
var name = myEnum.a.GetDescription() //will return '0101'
Assuming that the numbers in your enum always have a length of 4
you can use the following
var val = (int)myEnum.a).ToString().PadLeft(4, '0')

SequenceEqual different results

in the code below calling SequenceEqual on generic list return true (as expected) when List is defined with class generic type (EquatableClass.Equals<> is called).
If list is defined with IEquatable interface, Equals method is not called and result is false (object.Equals is called instead, not in code).
The question is, why the EquatableClass.Equals<> method is not called in the second case?
public class EquatableClass : IEquatable<EquatableClass>
{
public string Name { get; set; }
public bool Equals(EquatableClass other) => this.Name.Equals(other.Name);
}
static void Main(string[] args)
{
var A = new List<EquatableClass> { new EquatableClass { Name = "A" } };
var B = new List<EquatableClass> { new EquatableClass { Name = "A" } };
var result1 = A.SequenceEqual(B); // == true;
var AA = new List<IEquatable<EquatableClass>> { new EquatableClass { Name = "A" } };
var BB = new List<IEquatable<EquatableClass>> { new EquatableClass { Name = "A" } };
var result2 = AA.SequenceEqual(BB); // == false;
}
The SequenceEqual<T> method will try to see if it can convert T to IEquatable<T>, and if it can, it will use IEquatable<T>.Equals for equality.
When you have a List<EquatableClass> it will then try to convert EquatableClass to IEquatable<EquatableClass>, and that succeeds, so it uses the appropriate Equals method.
When you have a List<IEquatable<EquatableClass>> it will then try to convert IEquatable<EquatableClass> to IEquatable<IEquatable<EquatableClass>>, and that will fail, because the actual object doesn't implement IEquatable<IEquatable<EquatableClass>>, so it resorts to the default behavior of using object.Equals(object), which you don't override.

How to do not select case of two items if you have only one item in list

Maybe it is silly question, but I am quite new in C#. I have such a object:
MyStructure data = new MyStructure()
{
Name = "Test",
DateTime = "2017-07-14T00:00:00.000Z",
Items = new List<ItemsRequest>
{
new ItemsRequest() { Type = ItemsType.Green, Id = "0020012321" }
}
};
And I have method where I am validating such structure:
var badRequests = new Dictionary<Func<bool>, string>
{
[() => myStructure.Name == null] = "Name parameter cannot be empty or null string",
[() => (myStructure.ItemsRequest[0].Type == ItemsType.Green &&
myStructure.ItemsRequest[0].Id == myStructure.ItemsRequest[0].Id && yStructure.ItemsRequest[1].Type == ItemsType.Red &&
myStructure.ItemsRequest[1].Id == myStructure.ItemsRequest[1].Id)] = "could not be created with one with these types",
};
This validation DO NOT pass, because there aren't 2 items in my object, but I am getting such a error:
An exception of type 'System.ArgumentOutOfRangeException' occurred in
mscorlib.dll but was not handled in user code
Additional information: Index was out of range. Must be non-negative
and less than the size of the collection.
How can I handle it when I have only one item in list and do not get error? As I understand it is because of indexes [] .
Edit: When I have such a structure with one item:
MyStructure data = new MyStructure()
{
Name = "Test",
DateTime = "2017-07-14T00:00:00.000Z",
Items = new List<ItemsRequest>
{
new ItemsRequest() { Type = ItemsType.Green, Id = "0020012321" }
}
};
And in dictionary I validate only this function, it pass succesfully, because I select only one item with specific entries.
[() => myStructure.ItemsRequest.Select(x => x.Type == ItemsType.Green && x.Id == "0020012321").FirstOrDefault()] = "Type cannot be Green",
But is there are any possibilities to be independent of indexes and select as many as you like items from object?
The following code has been tested and works fine, if this doesn't work (as you previously mentioned on a comment) then there must be something else wrong with your code.
I have changed the Dictionary<Func<bool>, string> to Dictionary<Predicate<MyStructure>, string>, Predicate<T> will return true or false based on your condition - and it will take a parameter of T, which in this case will be a parameter of type MyStructure - this allows you to define your collection outside of the method.
class Program
{
// Create a Dictionary of Key type Predicate<MyStructure>, Predicate returns true or false - no need for a Func<bool>
private static Dictionary<Predicate<MyStructure>, string> badRequests = new Dictionary<Predicate<MyStructure>, string>
{
[p => string.IsNullOrWhiteSpace(p.Name)] = "Name parameter cannot be empty or null string",
[p => p.Items.Count == 2 /* <- This is important, as René pointed out in his answer */ && p.Items[0].Type == ItemsType.Green &&
p.Items[0].Id == "0020012321" && p.Items[1].Type == ItemsType.Red &&
p.Items[1].Id == "9023546547"
] = "could not be created with one with these types"
};
static void Main(string[] args)
{
// Initialize object
MyStructure data = new MyStructure()
{
Name = "Test",
DateTime = "2017-07-14T00:00:00.000Z",
Items = new List<ItemsRequest>
{
new ItemsRequest() { Type = ItemsType.Green, Id = "0020012321" },
new ItemsRequest() { Type = ItemsType.Red, Id = "9023546547" }
}
};
// Call badRequests Dictionary with data to fetch Value
string myString = badRequests.FirstOrDefault(p => p.Key.Invoke(data)).Value;
Console.ReadKey();
}
public class MyStructure
{
public string Name { get; set; }
public string DateTime { get; set; }
public List<ItemsRequest> Items { get; set; }
}
public class ItemsRequest
{
public string Id { get; set; }
public ItemsType Type { get; set; }
}
public enum ItemsType
{
Green, Red
}
}
Simply add test the Count of the list before accessing:
() => (myStructure.ItemsRequest.Count == 2 && // <-- add this line
myStructure.ItemsRequest[0].Type == ItemsType.Green &&
myStructure.ItemsRequest[0].Id == "0020012321" &&
myStructure.ItemsRequest[1].Type == ItemsType.Red &&
myStructure.ItemsRequest[1].Id == "9023546547")
The expression is evaluated "short-circuited", so if Count is not 2, the rest of the expression is not evaluated and so you don't try to access index 1 of the list if there is no second element.

Dynamic string interpolation

Can anyone help me with this?
Required Output: "Todo job for admin"
class Program
{
static void Main(string[] args)
{
Console.WriteLine(ReplaceMacro("{job.Name} job for admin", new Job { Id = 1, Name = "Todo", Description="Nothing" }));
Console.ReadLine();
}
static string ReplaceMacro(string value, Job job)
{
return value; //Output should be "Todo job for admin"
}
}
class Job
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Two suggestions:
DataBinder.Eval
string ReplaceMacro(string value, Job job)
{
return Regex.Replace(value, #"{(?<exp>[^}]+)}", match => {
return (System.Web.UI.DataBinder.Eval(new { Job = job }, match.Groups["exp"].Value) ?? "").ToString();
});
}
Linq.Expression
Use the Dynamic Query class provided in the MSDN LINQSamples:
string ReplaceMacro(string value, Job job)
{
return Regex.Replace(value, #"{(?<exp>[^}]+)}", match => {
var p = Expression.Parameter(typeof(Job), "job");
var e = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { p }, null, match.Groups["exp"].Value);
return (e.Compile().DynamicInvoke(job) ?? "").ToString();
});
}
In my opinion, the Linq.Expression is more powerful, so if you trust the input string, you can do more interesting things, i.e.:
value = "{job.Name.ToUpper()} job for admin"
return = "TODO job for admin"
You can't use string interpolation this way. But you can still use the pre-C#6 way to do it using string.Format:
static void Main(string[] args)
{
Console.WriteLine(ReplaceMacro("{0} job for admin", new Job { Id = 1, Name = "Todo", Description = "Nothing" }));
Console.ReadLine();
}
static string ReplaceMacro(string value, Job job)
{
return string.Format(value, job.Name);
}
This generic solution Extend the answer provided by #Dan
It can be used for any typed object.
install System.Linq.Dynamic
Install-Package System.Linq.Dynamic -Version 1.0.7
string ReplaceMacro(string value, object #object)
{
return Regex.Replace(value, #"{(.+?)}",
match => {
var p = Expression.Parameter(#object.GetType(), #object.GetType().Name);
var e = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { p }, null, match.Groups[1].Value);
return (e.Compile().DynamicInvoke(#object) ?? "").ToString();
});
}
See a working demo for a Customer type
You could use RazorEngine:
using RazorEngine;
class Program
{
static void Main(string[] args)
{
Console.WriteLine(ReplaceMacro("#Model.Name job for admin", new Job { Id = 1, Name = "Todo", Description="Nothing" }));
Console.ReadLine();
}
static string ReplaceMacro(string value, Job job)
{
return Engine.Razor.RunCompile(value, "key", typeof(Job), job);
}
}
It even supports Anonymous Types and method calls:
string template = "Hello #Model.Name. Today is #Model.Date.ToString(\"MM/dd/yyyy\")";
var model = new { Name = "Matt", Date = DateTime.Now };
string result = Engine.Razor.RunCompile(template, "key", null, model);
Little late to the party! Here is the one I wrote -
using System.Reflection;
using System.Text.RegularExpressions;
public static class AmitFormat
{
//Regex to match keywords of the format {variable}
private static readonly Regex TextTemplateRegEx = new Regex(#"{(?<prop>\w+)}", RegexOptions.Compiled);
/// <summary>
/// Replaces all the items in the template string with format "{variable}" using the value from the data
/// </summary>
/// <param name="templateString">string template</param>
/// <param name="model">The data to fill into the template</param>
/// <returns></returns>
public static string FormatTemplate(this string templateString, object model)
{
if (model == null)
{
return templateString;
}
PropertyInfo[] properties = model.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
if (!properties.Any())
{
return templateString;
}
return TextTemplateRegEx.Replace(
templateString,
match =>
{
PropertyInfo property = properties.FirstOrDefault(propertyInfo =>
propertyInfo.Name.Equals(match.Groups["prop"].Value, StringComparison.OrdinalIgnoreCase));
if (property == null)
{
return string.Empty;
}
object value = property.GetValue(model, null);
return value == null ? string.Empty : value.ToString();
});
}
}
Example -
string format = "{foo} is a {bar} is a {baz} is a {qux} is a really big {fizzle}";
var data = new { foo = 123, bar = true, baz = "this is a test", qux = 123.45, fizzle = DateTime.UtcNow };
Compared with other implementations given by Phil Haack and here are the results for the above example -
AmitFormat took 0.03732 ms
Hanselformat took 0.09482 ms
OskarFormat took 0.1294 ms
JamesFormat took 0.07936 ms
HenriFormat took 0.05024 ms
HaackFormat took 0.05914 ms
Wrap the string in a function...
var f = x => $"Hi {x}";
f("Mum!");
//... Hi Mum!
You need named string format replacement. See Phil Haack's post from years ago: http://haacked.com/archive/2009/01/04/fun-with-named-formats-string-parsing-and-edge-cases.aspx/
Not exactly but with bit tweek, I have created generic interpolation which support fields / property only.
public static string Interpolate(this string template, params Expression<Func<object, string>>[] values)
{
string result = template;
values.ToList().ForEach(x =>
{
MemberExpression member = x.Body as MemberExpression;
string oldValue = $"{{{member.Member.Name}}}";
string newValue = x.Compile().Invoke(null).ToString();
result = result.Replace(oldValue, newValue);
}
);
return result;
}
Test case
string jobStr = "{Name} job for admin";
var d = new { Id = 1, Name = "Todo", Description = "Nothing" };
var result = jobStr.Interpolate(x => d.Name);
Another
string sourceString = "I wanted abc as {abc} and {dateTime} and {now}";
var abc = "abcIsABC";
var dateTime = DateTime.Now.Ticks.ToString();
var now = DateTime.Now.ToString();
string result = sourceString.Interpolate(x => abc, x => dateTime, x => now);
Starting from the accepted answer I created a generic extension method:
public static string Replace<T>(this string template, T value)
{
return Regex.Replace(template, #"{(?<exp>[^}]+)}", match => {
var p = Expression.Parameter(typeof(T), typeof(T).Name);
var e = System.Linq.Dynamic.Core.DynamicExpressionParser.ParseLambda(new[] { p }, null, match.Groups["exp"].Value);
return (e.Compile().DynamicInvoke(value) ?? "").ToString();
});
}
Answer from #ThePerplexedOne is better, but if you really need to avoid string interpolation, so
static string ReplaceMacro(string value, Job job)
{
return value?.Replace("{job.Name}", job.Name); //Output should be "Todo job for admin"
}
You should change your function to:
static string ReplaceMacro(Job obj, Func<dynamic, string> function)
{
return function(obj);
}
And call it:
Console.WriteLine(
ReplaceMacro(
new Job { Id = 1, Name = "Todo", Description = "Nothing" },
x => $"{x.Name} job for admin"));
If you really need this, you can do it using Roslyn, create string – class implementation like
var stringToInterpolate = "$#\"{{job.Name}} job for admin\"";
var sourceCode = $#"
using System;
class RuntimeInterpolation(Job job)
{{
public static string Interpolate() =>
{stringToInterpolate};
}}";
then make an assembly
var assembly = CompileSourceRoslyn(sourceCode);
var type = assembly.GetType("RuntimeInterpolation");
var instance = Activator.CreateInstance(type);
var result = (string) type.InvokeMember("Interpolate",
BindingFlags.Default | BindingFlags.InvokeMethod, null, instance, new object[] {new Job { Id = 1, Name = "Todo", Description="Nothing" }});
Console.WriteLine(result);
you'll get this code by runtime and you'll get your result (also you'll need to attach link to your job class to that assembly)
using System;
class RuntimeInterpolation(Job job)
{
public static string Interpolate() =>
$#"{job.Name} job for admin";
}
You can read about how to implement CompileSourceRoslyn here Roslyn - compiling simple class: "The type or namespace name 'string' could not be found..."
Wondering no one has mentioned mustache-sharp. Downloadable via Nuget.
string templateFromSomewhere = "url: {{Url}}, Name:{{Name}}";
FormatCompiler compiler = new FormatCompiler();
Generator generator = compiler.Compile(templateFromSomewhere);
string result = generator.Render(new
{
Url="https://google.com",
Name = "Bob",
});//"url: https://google.com, Name:Bob"
More examples could be found here, at the unit testing file.
The implementation I'd come up with very similar to giganoide's allowing callers to substitute the name used in the template for the data to interpolate from. This can accommodate things like feeding it anonymous types. It will used the provided interpolateDataAs name if provided, otherwise if it isn't an anonymous type it will default to the type name, otherwise it expects "data". (or specifically the name of the property) It's written as an injectable dependency but should still work as an extension method.
public interface ITemplateInterpolator
{
/// <summary>
/// Attempt to interpolate the provided template string using
/// the data provided.
/// </summary>
/// <remarks>
/// Templates may want to use a meaninful interpolation name
/// like "enquiry.FieldName" or "employee.FieldName" rather than
/// "data.FieldName". Use the interpolateDataAs to pass "enquiry"
/// for example to substitute the default "data" prefix.
/// </remarks>
string? Interpolate<TData>(string template, TData data, string? interpolateDataAs = null) where TData : class;
}
public class TemplateInterpolator : ITemplateInterpolator
{
/// <summary>
/// <see cref="ITemplateInterpolator.Interpolate(string, dynamic, string)"/>
/// </summary>
string? ITemplateInterpolator.Interpolate<TData>(string template, TData data, string? interpolateDataAs)
{
if (string.IsNullOrEmpty(template))
return template;
if (string.IsNullOrEmpty(interpolateDataAs))
{
interpolateDataAs = !typeof(TData).IsAnonymousType() ? typeof(TData).Name : nameof(data);
}
var parsed = Regex.Replace(template, #"{(?<exp>[^}]+)}", match =>
{
var param = Expression.Parameter(typeof(TData), interpolateDataAs);
var e = System.Linq.Dynamic.Core.DynamicExpressionParser.ParseLambda(new[] { param }, null, match.Groups["exp"].Value);
return (e.Compile().DynamicInvoke(data) ?? string.Empty).ToString();
});
return parsed;
}
For detecting the anonymous type:
public static bool IsAnonymousType(this Type type)
{
if (type.IsGenericType)
{
var definition = type.GetGenericTypeDefinition();
if (definition.IsClass && definition.IsSealed && definition.Attributes.HasFlag(TypeAttributes.NotPublic))
{
var attributes = definition.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false);
return (attributes != null && attributes.Length > 0);
}
}
return false;
}
Test Suite:
[TestFixture]
public class WhenInterpolatingATemplateString
{
[TestCase("")]
[TestCase(null)]
public void ThenEmptyValueReturedWhenNoTemplateProvided(string? template)
{
ITemplateInterpolator testInterpolator = new TemplateInterpolator();
var testData = new TestData { Id = 14, Name = "Test" };
var result = testInterpolator.Interpolate(template!, testData);
Assert.That(result, Is.EqualTo(template));
}
[Test]
public void ThenTheTypeNameIsUsedForTheDataReferenceForDefinedClasses()
{
ITemplateInterpolator testInterpolator = new TemplateInterpolator();
var testData = new TestData { Id = 14, Name = "Test" };
string template = "This is a record named \"{TestData.Name}\" with an Id of {testdata.Id}."; // case insensitive.
string expected = "This is a record named \"Test\" with an Id of 14.";
var result = testInterpolator.Interpolate(template, testData);
Assert.That(result, Is.EqualTo(expected));
}
[Test]
public void ThenTheDefaultNameIsUsedForTheDataReferenceForAnonymous()
{
ITemplateInterpolator testInterpolator = new TemplateInterpolator();
var testData = new { Id = 14, Name = "Test" };
string template = "This is a record named \"{data.Name}\" with an Id of {data.Id}.";
string expected = "This is a record named \"Test\" with an Id of 14.";
var result = testInterpolator.Interpolate(template, testData);
Assert.That(result, Is.EqualTo(expected));
}
[Test]
public void ThenTheProvidedDataReferenceNameOverridesTheTypeName()
{
ITemplateInterpolator testInterpolator = new TemplateInterpolator();
var testData = new TestData { Id = 14, Name = "Test" };
string template = "This is a record named \"{otherData.Name}\" with an Id of {otherData.Id}.";
string expected = "This is a record named \"Test\" with an Id of 14.";
var result = testInterpolator.Interpolate(template, testData, "otherData");
Assert.That(result, Is.EqualTo(expected));
}
[Test]
public void ThenExceptionIsThrownWhenTemplateReferencesUnknownDataValues()
{
ITemplateInterpolator testInterpolator = new TemplateInterpolator();
var testData = new TestData { Id = 14, Name = "Test" };
string template = "This is a record named \"{testData.Name}\" with an Id of {testData.Id}. {testData.ExtraDetails}";
Assert.Throws<ParseException>(() => { var result = testInterpolator.Interpolate(template, testData, "testData"); });
}
[Test]
public void ThenDataFormattingExpressionsAreApplied()
{
ITemplateInterpolator testInterpolator = new TemplateInterpolator();
var testData = new { Id = 14, Name = "Test", IsActive = true, EffectiveDate = DateTime.Today };
string template = "The active state is {data.IsActive?\"Yes\":\"No\"}, Effective {data.EffectiveDate.ToString(\"yyyy-MM-dd\")}";
string expected = "The active state is Yes, Effective " + DateTime.Today.ToString("yyyy-MM-dd");
var result = testInterpolator.Interpolate(template, testData);
Assert.That(result, Is.EqualTo(expected));
}
private class TestData
{
public int Id { get; set; }
public string Name { get; set; }
}
}
I really don't understand the point of your ReplaceMacro method...
But here's how it should work:
class Program
{
static void Main(string[] args)
{
var job = new Job { Id = 1, Name = "Todo", Description = "Nothing" };
Console.WriteLine($"{job.Name} job for admin");
Console.ReadLine();
}
}
But if you really want the dynamic feel to it, your ReplaceMacro method should just take one parameter, which is the job:
static string ReplaceMacro(Job job)
{
return $"{job.Name} job for admin.";
}
And use it like:
var job = new Job { Id = 1, Name = "Todo", Description = "Nothing" };
Console.WriteLine(ReplaceMacro(job));
Or something to that effect.

Parsing string as enum

Consider the enum below:
public enum TestType
{
Mil,
IEEE
}
How can I parse the folowing strings to the above enum?
Military 888d Test in case of TestType.Mil
Or
IEEE 1394 in case of TestType.IEEE
My idea was to check if the first letters of string matches 'Mil' or 'IEEE' then I would set it as the enum I want, but the problemis there are other cases that should not be parsed!
Already answred by me : How to set string in Enum C#?
Enum cannot be string but you can attach attribute and than you can read the value of enum as below....................
public enum TestType
{
[Description("Military 888d Test")]
Mil,
[Description("IEEE 1394")]
IEEE
}
public static string GetEnumDescription(Enum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(
typeof(DescriptionAttribute),
false);
if (attributes != null &&
attributes.Length > 0)
return attributes[0].Description;
else
return value.ToString();
}
here is good article if you want to go through it : Associating Strings with enums in C#
My understanding of your situation is that the string can come in any format. You could have strings like "Military 888d Test", "Mil 1234 Test", "Milit xyz SOmething"...
In such a scenario, a simple Enum.Parse is NOT going to help. You will need to decide for each value of the Enum, which combinations do you want to allow. Consider the code below...
public enum TestType
{
Unknown,
Mil,
IEEE
}
class TestEnumParseRule
{
public string[] AllowedPatterns { get; set; }
public TestType Result { get; set; }
}
private static TestType GetEnumType(string test, List<TestEnumParseRule> rules)
{
var result = TestType.Unknown;
var any = rules.FirstOrDefault((x => x.AllowedPatterns.Any(y => System.Text.RegularExpressions.Regex.IsMatch(test, y))));
if (any != null)
result = any.Result;
return result;
}
var objects = new List<TestEnumParseRule>
{
new TestEnumParseRule() {AllowedPatterns = new[] {"^Military \\d{3}\\w{1} [Test|Test2]+$"}, Result = TestType.Mil},
new TestEnumParseRule() {AllowedPatterns = new[] {"^IEEE \\d{3}\\w{1} [Test|Test2]+$"}, Result = TestType.IEEE}
};
var testString1 = "Military 888d Test";
var testString2 = "Miltiary 833d Spiral";
var result = GetEnumType(testString1, objects);
Console.WriteLine(result); // Mil
result = GetEnumType(testString2, objects);
Console.WriteLine(result); // Unknown
The important thing is populating that rules object with the relevant regular expressions or tests. How you get those values in to the array, really depends on you...
try this var result = Enum.Parse(type, value);
Please try this way
TestType testType;
Enum.TryParse<TestType>("IEEE", out testType);
and it you want to compare string then
bool result = testType.ToString() == "IEEE";
Simple method will do it for you:
public TestType GetTestType(string testTypeName)
{
switch(testTypeName)
{
case "Military 888d Test":
return TestType.Mil;
case "IEEE 1394":
return TestType.IEEE;
default:
throw new ArgumentException("testTypeName");
}
}
EDIT
use Enum.GetNames:
foreach (var value in Enum.GetNames(typeof(TestType)))
{
// compare strings here
if(yourString.Contains(value))
{
// what you want to do
...
}
}
If you using .NET4 or later you can use Enum.TryParse. and Enum.Parse is available for .NET2 and later.

Categories

Resources