Often enough we have frameworks do heavy lifting for us. That's usually a good thing. But I always found it kind of sedating to let "the man" provide me with too much comfort. Especially when the framework or other SOUP library fails to perform exactly what you want. That usually leads to some grumpiness followed by some code-acrobatics to punch that square peg into a round hole – often negating the elegance and usefulness of the library call altogether.
One task a framework often performs is binding. Binding – as defined here – is taking an object and assigning values to it's properties from a property bag of sorts. For example, a property bag could be a hash-table or list of name-value pairs and an object would be a POCO (Plain old class object). It's common in web and ORM contexts, also in web services to receive a dictionary of values and have to attach them to your domain model. The plain straightforward way would be to pluck key value pairs one by one by name and assign to your object. But that's no fun and error prone. What we would like is to be able to auto-attach values to the target object.
How do we achieve that? Steve Bearman and I actually presented this very briefly as part of a larger talk on advanced C# at SoCal Code Camp last weekend. A bit of reflection, a bit of generics and extensions, and viola:
1: public static T Populate<T>(this T target, IEnumerable<KeyValuePair<string, object>> values)
2: {
3: Type targetType = target.GetType();
4: foreach (var kvp in values)
5: {
6: PropertyInfo pi = targetType.GetProperty(kvp.Key);
7: pi.SetValue(target, kvp.Value, null);
8: }
9: return target;
10: }
The extension method Populate() above takes an IEnumerable of KeyValuePair as a parameter. It then iterates a these pairs and for each key finds a property by that name and assigns the value from the dictionary to the target object. Reflection comes in at lines 6 and 7. A property is found based on the name alone, and assigned to. Usage of this extension can look something like this:
1: Dictionary<string, object> assignments = new Dictionary<string, object>();
2: assignments.Add("Name", "Paris Hilton");
3: assignments.Add("ID", 42);
4: assignments.Add("HomePhone", "(310)-555-1212");
5: assignments.Add("WorkPhone", "(310)-777-FILM");
6: Person paris = new Person();
7:
8: Person result = paris.Populate(assignments);
Simple, granted. Simplistic perhaps. But let's consider that the average programmer doesn't write much code like this. There can be many bells and whistles added here: A property getter might only assign if the prperty is writable, attempt to match without case sensitivity, only match if property is public or adorned with an attribute. The main thinks is that you can write this method into your solution in very few lines and shape it as you wish. You do not have to depend on default MVC binders or ORMs being open source or anything like that to have binding performed. Further utilizing this bit (!) of code you might create a simple object factory that returns a new populated type from any object given a property bag of values. In fact, let's do this right here:
1: public static class MyFactory<T>
2: {
3: public static T Create(IEnumerable<KeyValuePair<string, object>> values)
4: {
5: T result;
6: ConstructorInfo ctor = typeof(T).GetConstructor(System.Type.EmptyTypes);
7: result = (T)ctor.Invoke(null);
8: result.Populate(values);
9: return result;
10: }
11: }
The generic factory MyFactory has a Create() method which takes a property bag as a parameter and returns a populated instance with the values provided. The usage of the factory eliminates the need for creating a new empty instance before populating it. Well, actually , it does it so you don't have to – code was not eliminated, just consolidated. The usage then becomes:
1: Dictionary<string, object> values = new Dictionary<string, object>();
2: values.Add("Name", "Paris Hilton");
3: values.Add("ID", 42);
4: values.Add("HomePhone", "(310)-555-1212");
5: values.Add("WorkPhone", "(310)-777-FILM");
6:
7: Person actual = MyFactory<Person>.Create(values);
So there we have it folks. Another DIY tidbit that would hopefully help you take control of the common binding task away from framework writers and back into your hands - where it belongs.
In production, you would probably want to also take a look at whether there's a Populate() or TryPopulate() -exception or return boolean - and handle the whole error pathing in an explicit way that fits your own style of coding. Similarly you should consider whether an unassigned property (missing value in the bag) is cause for error and whether extra unmatched values are cause for error. In this example, an extra value will cause an exception and an unassigned property will not.
Happy Binding!
4cda0bba-adfc-4c3a-b485-ebfa52aa6e02|0|.0
Sometimes I just write code. And sometimes I clean up my code. Most of the times, I focus on the meat of the methods, hacking away at verbose or lengthy flows, re-factoring out common code, trying to untangle overly complex logic etc.
Recently, I noticed that many of the conditional terms I write span very long lines and are a bit cumbersome to read. The reason for that is that many of the variable names are long, or the properties or both and that often the comparison is on a property of an object or member of a collection etc.
So for instance:
if (summerCatalog.Products[0].ProductCategories[0].ParentCategoryID >= 1 && summerCatalog.Products[0].ProductCategories[0].ParentCategoryID <= 4)
{
//...
}
Can become quite long.
Long is not easy to read.
Long is not easy to maintain.
Long is not easy to think through.
What I really wanted to say is if [value] is between [a] and [b].
Of course, one could say "lets just make the variable names shorter". But that flies in the face of self explanatory plain naming. So abbreviating for the sake of short-lineliness (New word! You heard it her first!) is out.
Well, this is just screaming "EXTENSION METHODS" doesn't it?
Here we go then:
/// <summary>
/// Returns whether the value is between the 2 specified boundaries.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value">The value to compare</param>
/// <param name="minValueInclusive">The min value (inclusive).</param>
/// <param name="maxValueInclusive">The max value (inclusive).</param>
/// <returns>True if the value is equal to or between the boundaries; False otherwise.</returns>
public static bool Between<T>(this T value, T minValueInclusive, T maxValueInclusive) where T : IComparable<T>
{
if (minValueInclusive.CompareTo(maxValueInclusive) > 0)
{
throw new ArgumentException("minimum value must not be greater than maximum value");
}
return (value.CompareTo(minValueInclusive) >= 0 && value.CompareTo(maxValueInclusive) <= 0);
}
The Between function takes any object which supports IComparable<T> and performs syntactic sugar comparison with the inclusive min and max values.
What's more, it adds a basic sanity check for the comparison. How many times do I do that sanity check in my normal code (!)?
Now the conditional is
if (summerCatalog.Products[0].ProductCategories[0].ParentCategoryID.Between(1, 4))
{
///...
}
At least I don't have to refactor this in my brain while reading.
Sure, you might say, but you could have just de-referenced the deep value and then have a shorter conditional, like so:
Category category = summerCatalog.Products[0].ProductCategories[0];
if (category.ParentCategoryID >= 1 && category.ParentCategoryID <= 4)
{
//...
}Yes - of course - but it adds a line of code, places the burden of reading the very common idiom ( x >= a && x <= b) on me, and I constantly stumble on the lest-than-or-equal vs. less-than and it doesn't check for my boundaries being inadvertently swapped.
So there you have it. A simple extension cuts down your lines of code, makes long text shorter and saves lives. Which begs the question: is this already part of the language - and should it be?
a54628f4-2ccd-44e7-b039-ece0c3807dfc|0|.0
So I wrote the thing up. Compared with a fairly common alternative, a run of the more type safe and render friendly template took 260 ms to merge 10k iterations vs. 480 ms for the alternative. In addition, the alternative went through almost twice the allocations (meaning more GC would result).
I'm kind of pleased that this is faster than the alternative, but am going to do some more thinking of a better way. A 46% reduction is nice, but it's just under twice as fast. Still, the benefits of a cached template and token/key checking beats the alternative.
Toying with the idea of a strongly typed template leads to a dead end pretty much. Sure, one could dynamically emit a class that exposes the token names as properties, but then how would you bind to these in compile time? since the tokens are NOT known at compile time, the programmer using the class would have to populate a name value pair in some fashion, so that at run time the binding could be made.
//------------------------------------
namespace Nuri.FasterTemplate
{
public interface IFasterTemplate
{
string ID { get; }
void Render(System.IO.TextWriter writer, IRuntimeValues runtimeValues);
IRuntimeValues GetRuntimeValuesPrototype();
}
}
//------------------------------------
namespace Nuri.FasterTemplate
{
public interface IRuntimeValues
{
bool AddValue(string tokenName, string value);
bool AddValues(IEnumerable values);
string[] GetAllowedValues();
IRuntimeValues GetCopy();
bool IsAlowed(string tokenName);
string this[string tokenName] { get; }
string ID { get; }
}
}
//------------------------------------
namespace Nuri.FasterTemplate
{
public static class FasterTempateFactory
{
public static IFasterTemplate GetFasterTemplate(TokenConfiguration config, ref string template)
{
string id = Guid.NewGuid().ToString();
TemplateParts templateParts = processTemplateParts(id, config, ref template);
IRuntimeValues runtimeValuesPrototype = processTokens(id, templateParts);
IFasterTemplate result = new FasterTemplate(id, templateParts, runtimeValuesPrototype);
return result;
}
private static IRuntimeValues processTokens(string id, TemplateParts templateParts)
{
RuntimeValues result = new RuntimeValues(id);
for (int i = 0; i < templateParts.Count; i++)
if (templateParts[i].IsToken)
result[templateParts[i].Value] = string.Empty;
return result;
}
private static TemplateParts processTemplateParts(string id, TokenConfiguration config, ref string template)
{
TemplateParts result = new TemplateParts();
char currentChar;
bool isToken = false;
StringBuilder sbText = new StringBuilder(template.Length / 2);
StringBuilder sbToken = new StringBuilder(64);
for (int idx = 0; idx < template.Length; idx++)
{
currentChar = template[idx];
if (currentChar == config.TokenStartMarker)
{ isToken = true; result.Add(new TemplatePart(sbText.ToString(), false)); sbText.Length = 0; }
else if (currentChar == config.TokenEndMarker) { isToken = false; result.Add(new TemplatePart(sbToken.ToString(), true)); sbToken.Length = 0; } else { if (isToken) sbToken.Append(currentChar); else sbText.Append(currentChar); }
} if (isToken == true) throw new ArgumentException("Template has unclosed token marker"); if (sbText.Length > 0) result.Add(new TemplatePart(sbText.ToString(), false)); return result;
}
}
}
//------------------------------------
namespace Nuri.FasterTemplate
{
internal class FasterTemplate : IFasterTemplate
{
private string _ID;
private TemplateParts _TemplateParts;
private IRuntimeValues _RuntimeValuesPrototype;
internal FasterTemplate(string ID, TemplateParts templateParts, IRuntimeValues runtimeValuesPrototype) { _ID = ID; _TemplateParts = templateParts; _RuntimeValuesPrototype = runtimeValuesPrototype; } public IRuntimeValues GetRuntimeValuesPrototype() { return _RuntimeValuesPrototype.GetCopy(); } public string ID { get { return _ID; } }
public void Render(System.IO.TextWriter writer, IRuntimeValues runtimeValues)
{
if (runtimeValues.ID != this._ID) throw new ArgumentException("The runtime values supplied are not compatible with this template! Ensure you got the runtime values object from the template with ID " + this._ID); for (int i = 0, count = _TemplateParts.Count; i < count; i++)
{
TemplatePart part = _TemplateParts[i]; if (part.IsToken) { writer.Write(runtimeValues[part.Value]); }
else
{
writer.Write(part.Value);
}
}
}
}
}
//------------------------------------
namespace Nuri.FasterTemplate
{
internal class RuntimeValues : Nuri.FasterTemplate.IRuntimeValues
{
private Dictionary<string,string> _AllowedValues;
internal string _ID;
internal RuntimeValues(string ID, int capacity)
{
_AllowedValues = new Dictionary<string, string>(capacity);
_ID = ID;
}
internal RuntimeValues(string ID) : this(ID, 0x10) { }
internal Dictionary<string,string> AllowedValues
{
get { return _AllowedValues; }
}
public string[] GetAllowedValues()
{
string[] result = new string[_AllowedValues.Count];
int i = 0;
foreach (string key in _AllowedValues.Keys)
{ result[i++] = key; }
return result;
}
public bool IsAlowed(string tokenName)
{
return _AllowedValues.ContainsKey(tokenName);
}
public bool AddValue(string tokenName, string value)
{
if (_AllowedValues.ContainsKey(tokenName))
{
_AllowedValues[tokenName] = value;
return true;
}
else
return false;
}
public bool AddValues(IEnumerable values)
{
bool result = true;
foreach (KeyValuePair<string, string> pair in values)
{
result = result && this.AddValue(pair.Key, pair.Value);
}
return result;
}
public string this[string tokenName]
{
get
{
return _AllowedValues[tokenName];
}
internal set
{ _AllowedValues[tokenName] = value; }
}
public IRuntimeValues GetCopy()
{
RuntimeValues result = new RuntimeValues(this._ID, _AllowedValues.Count);
foreach (string key in _AllowedValues.Keys)
result.AllowedValues[key] = string.Empty;
return result;
}
public string ID
{
get { return _ID; }
}
}
}
//------------------------------------
namespace Nuri.FasterTemplate
{
internal struct TemplatePart
{
public bool IsToken;
public string Value;
public TemplatePart(string value, bool isToken)
{
this.Value = value;
this.IsToken = isToken;
}
}
}
//------------------------------------
namespace Nuri.FasterTemplate
{
class TemplateParts : List<TemplatePart> { }
}
//------------------------------------
namespace Nuri.FasterTemplate
{
public struct TokenConfiguration
{
public readonly char TokenStartMarker;
public readonly char TokenEndMarker;
public TokenConfiguration(char tokenStartMarker, char tokenEndMarker)
{
TokenStartMarker = tokenStartMarker;
TokenEndMarker = tokenEndMarker;
}
}
}
6a6979f0-1f94-441a-8241-3dcfab162c78|0|.0
You know you have a problem when your app behaves badly.
You know you should do something about it when a customer complains
You know you are late when your competitor took away your customer.
663ed641-a454-4cd7-a176-59a0f92e8fc5|2|3.5
The new yields statement sounds very convenient. In the past, one had to write a significant amount of code to implement the IEnumerator interface and expose an enumerator. That included considerations of concurrency, a loop variable bound to the instance or other methods to maintain current loop value during enumeration.
Fret no more, a new syntax is in town - the yields statement.
With the yields statement, IEnumerator implementation folds down to a scan one liner:
public class MyCollection : IEnumerable
{
public IEnumerator GetEnumerator()
{
foreach (string s in new string [] {"Larry","Moe","Curley"})
{
yield return s + " is a stooge";
}
}
}
You can also provide an enumerator returning static values or "hard coded" number of values:
public class VerySimple : IEnumerable
{
public List<DateTime> _Things;
public IEnumerator GetEnumerator()
{
yield return 1;
yield return 7;
yield return 11;
}
}
So that sounds great! No pesky Reset(), MoveNext() etc., no private index to hold on to, and even options to do more fancy things, like exposing only some of your items to enumeration:
public class Person
{
public string Name;
public bool IsPublic;
public Person(string name, bool isPublic)
{
this.Name = name;
this.IsPublic = isPublic;
}
}
public class People : IEnumerable
{
private Person[] _Peeps =new Person[] {
new Person("James Brown", true),
new Person("John Lenon", true),
new Person("Johnny Doe", false)
};
public IEnumerator GetEnumerator()
{
foreach (Person dude in _Peeps)
{
if (dude.IsPublic)
{
yield return dude.Name + " is a well known";
}
}
}
}
That was easy, and pretty useful too. You get to have an easy syntax for emitting each value, and you get exact control over which item is exposed without implementing a whole sub class just for the enumeration.
Looking at this keyword and the simplicity of exposing enumerator one might be tempted to think there is some magic new framework for enumerating a collection with hooks and generic loops or something. To find out, I looked at the IL generated for the MyCollection class we just created.
As expected, we find the class has a method named GetEnumerator(). It's implementation is seemingly simple, instantiate some cryptically named class and return it.
public IEnumerator GetEnumerator()
{
<GetEnumerator>d__0 d__ = new <GetEnumerator>d__0(0);
d__.<>4__this = this;
return d__;
}
When you look at the implementation of the enumerator class itself, you get quite a few lines of code:
private sealed class <GetEnumerator>d__0 : IEnumerator<object>, IEnumerator, IDisposable
{
// Fields
private int <>1__state;
private object <>2__current;
public MyCollection <>4__this;
public string[] <>7__wrap2;
public int <>7__wrap3;
public string <s>5__1;
// Methods
public <GetEnumerator>d__0(int <>1__state)
{
this.<>1__state = <>1__state;
}
private bool MoveNext()
{
try
{
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
this.<>1__state = 1;
this.<>7__wrap2 = new string[] { "Larry", "Moe", "Curley" };
this.<>7__wrap3 = 0;
while (this.<>7__wrap3 < this.<>7__wrap2.Length)
{
this.<s>5__1 = this.<>7__wrap2[this.<>7__wrap3];
this.<>2__current = this.<s>5__1 + " is a stooge";
this.<>1__state = 2;
return true;
Label_0098:
this.<>1__state = 1;
this.<>7__wrap3++;
}
this.<>1__state = -1;
break;
case 2:
goto Label_0098;
}
return false;
}
fault
{
this.Dispose();
}
}
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
void IDisposable.Dispose()
{
switch (this.<>1__state)
{
case 1:
case 2:
this.<>1__state = -1;
break;
}
}
// Properties
object IEnumerator<object>.Current
{
get
{
return this.<>2__current;
}
}
object IEnumerator.Current
{
get
{
return this.<>2__current;
}
}
}
So what is really going on here is that when you type out "yield return x;" the compiler transforms this into a method stub, implants your loop logic in the MoveNext() method of a new shiny enumerator class, and provides the standard requisite functions of IEnumerable interface which support the foreach statement.
Is this good or bad? Certainly it serves well in many instances. For most of your daily uses for an enumerator this should work quite well. It's strongly typed to the list item and uses your class's values referenced directly.
What can be sub optimal about this? Multithreaded applications need to implement locking at the class level. Some collections in .NET implement an internal version number such that if the collection chances during enumeration an exception gets thrown to the enumerating thread. Not so here. If you want that behavior you'd have to implement it yourself.
You should note that the loop itself and any of your conditions get transformed by the compiler. The transformation, I trust, is functionally equivalent. The transformation result will vary slightly based on the collection being iterated, or if you are using a static chain of yield statements. In the case of hard coded yielded values, no concurrency issues should arise, but that is fairly rare in my humble experience.
Besides that, I think it's pretty cool. You get to write less code, the compiler take care of your code generation.
On a side note, when decompiling your code, don't get too caught up in Reflector's code rendering. For one, IL decompiled to your language of choice is not a symmetric operation. For that reason and due to compiler optimizations and inlining, certain language constructs may come up reflected as GOTO label but were not necessarily coded this way originally in the higher level language.
1371e3b4-8ab8-464d-9b47-cff3e96d6539|0|.0
I was challenged recently with a question about generics with constraints.
The claim was that it's only a compile flaw that allows your to declare an interface for a generic type with a constraint. Namely that the syntax
public interface ISomething <T> where T: SomeClass
would pass compilation but would not be useful (runtime) because you can't declare a variable
ISomething myVar = new ISomething<SomeClass>();
or something to that extent. I went home feeling a bit uneasy about the discussion, then coded this up the way I see it. While it is completely true that you can't 'new' an interface, using an interface that has a generic type is completely possible and legal.
Here it is in all it's g(l)ory.
using System;
using System.IO;
using System.Text;
/// Demo of generic interface declaration with constraint.
/// Showing compilation and runtime feasibility.
namespace NH.Demo
{
class Demo
{
static void Main(string[] args)
{
ITryInstance o1 = new GenericInstanceA();
ITry<MyDerivedType> o2 = new GenericInstanceB();
Console.WriteLine("Generic instance 1 " + o1.ProperT.SomeField);
Console.WriteLine("Generic instance 2 " + o2.ProperT.SomeField);
}
}
interface ITry<T> where T : MyBaseType
{
T ProperT{ get; }
}
public class MyBaseType
{
public string SomeField;
}
public class MyDerivedType : MyBaseType
{
public MyDerivedType(string arg)
{
base.SomeField = arg;
}
}
interface ITryInstance : ITry<MyDerivedType>
{
}
/// <summary>
/// this will fail. cosntraint violation
/// "The type 'string' must be convertible
/// to 'NH.Demo.MyBaseType' in order to use it as
/// parameter 'T' in the generic type or
/// method 'NH.Demo.ITry<T>'"
/// </summary>
//interface IFail : ITry<string> { }
public class GenericInstanceA : ITryInstance
{
MyDerivedType ITry<MyDerivedType>.ProperT
{
get { return new MyDerivedType("hi there!"); }
}
}
public class GenericInstanceB : ITry<MyDerivedType>
{
MyDerivedType ITry<MyDerivedType>.ProperT
{
get { return new MyDerivedType("hi there! again"); }
}
}
}
52f9f371-40d4-47b5-a9db-dbe66c6f84a5|0|.0
I was doing some workflow work and wanted to create a custom loop activity. The project needs it, and it's a great way to learn what (not) to do.
The activity is a container that would loop through a list of discrete items (think foreach(string currentValue in ValueList)) and exposes the current loop variable via a bindable DependencyProperty.
The basics of the activity are to keep the container in "Executing" mode until all child activities are done. The tricky part is that the whole ActivityExecutionContext and ExecutionContextManager need to create for a new context each loop iteration. The hookup of the synchronization is done by using Activity.RegisterForStatusChange(.., OnEvent) on each child executed, then in the OnEvent() unregister the activity from further notice. I don't love it, but it works.
Here goes:
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Design;
namespace NH.Workflow
{
[Designer(typeof(SequenceDesigner),
typeof(IDesigner)),
ToolboxItem(typeof(ActivityToolboxItem)),
Description("Loop Activity - iterate over discrete list of items."),
ActivityValidator(typeof(LoopActivityValidator))]
public sealed class LoopActivity : CompositeActivity, IActivityEventListener<ActivityExecutionStatusChangedEventArgs>
{
private int currentIndex = 0;
private string[] valueList = { };
protected override ActivityExecutionStatus Cancel(ActivityExecutionContext executionContext)
{
if (base.EnabledActivities.Count == 0)
return ActivityExecutionStatus.Closed;
Activity firstChildActivity = base.EnabledActivities[0];
ActivityExecutionContext firstChildContext = executionContext.ExecutionContextManager.GetExecutionContext(firstChildActivity);
if (firstChildContext == null)
return ActivityExecutionStatus.Closed;
if (firstChildContext.Activity.ExecutionStatus == ActivityExecutionStatus.Executing)
firstChildContext.CancelActivity(firstChildContext.Activity);
return ActivityExecutionStatus.Canceling;
}
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
if (this.PerformNextIteration(executionContext))
return ActivityExecutionStatus.Executing;
else
return ActivityExecutionStatus.Closed;
}
void IActivityEventListener<ActivityExecutionStatusChangedEventArgs>.OnEvent(object sender, ActivityExecutionStatusChangedEventArgs statusChangeEvent)
{
ActivityExecutionContext originContext = sender as ActivityExecutionContext;
statusChangeEvent.Activity.UnregisterForStatusChange(Activity.ClosedEvent, this);
ActivityExecutionContextManager ctxManager = originContext.ExecutionContextManager;
ctxManager.CompleteExecutionContext(ctxManager.GetExecutionContext(statusChangeEvent.Activity));
if (!this.PerformNextIteration(originContext))
originContext.CloseActivity();
}
private bool PerformNextIteration(ActivityExecutionContext context)
{
if (((base.ExecutionStatus == ActivityExecutionStatus.Canceling)
|| (base.ExecutionStatus == ActivityExecutionStatus.Faulting))
|| currentIndex == valueList.Length)
{
return false;
}
this.CurrentValue = valueList[currentIndex++];
if (base.EnabledActivities.Count > 0)
{
ActivityExecutionContext firstChildContext = context.ExecutionContextManager.CreateExecutionContext(base.EnabledActivities[0]);
firstChildContext.Activity.RegisterForStatusChange(Activity.ClosedEvent, this);
firstChildContext.ExecuteActivity(firstChildContext.Activity);
}
return true;
}
public static DependencyProperty ValueListProperty = System.Workflow.ComponentModel.DependencyProperty.Register("ValueList", typeof(string[]), typeof(LoopActivity));
/// <summary>
/// The list of values to iterate over. Child activities would be executed for each value in this list, and would be able to access the current value via the CurrentValue property.
/// </summary>
[Description("The values to iterate over")]
[Category("Other")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string[] ValueList
{
internal get
{
return valueList;
}
set
{
valueList = value;
}
}
public static DependencyProperty CurrentValueProperty = System.Workflow.ComponentModel.DependencyProperty.Register("CurrentValue", typeof(string), typeof(LoopActivity));
/// <summary>
/// The current value of the loop variable. This value changes each iteration and is used by child activities interested in the iteration value.
/// </summary>
[Description("The current loop value. Child activities should bind to this value if they are using the loop variable.")]
[Category("Other")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string CurrentValue
{
get
{
return ((string)(base.GetValue(LoopActivity.CurrentValueProperty)));
}
private set
{
base.SetValue(LoopActivity.CurrentValueProperty, value);
}
}
}
/// <summary>
/// Validator for the loop activity.
/// Check that the list of discrete items to iterate over is valid.
/// </summary>
public class LoopActivityValidator : ActivityValidator
{
public override ValidationErrorCollection ValidateProperties(ValidationManager manager, object obj)
{
ValidationErrorCollection errors = new ValidationErrorCollection();
LoopActivity activityToValidate = obj as LoopActivity;
if (activityToValidate.Parent != null) // prevent compile time checking.
{
if (activityToValidate == null)
errors.Add(new ValidationError("object passed in is not a LoopActivity", 1));
if (activityToValidate.ValueList == null)
errors.Add(new ValidationError("Value List not provided (it is null). Please provide a list of values to iterate over.", 2));
if (activityToValidate.ValueList.Length == 0)
errors.Add(new ValidationError("Value List not provided (it is empty). Please provide a list of values to iterate over.", 3));
}
return errors;
}
}
}
0618d531-135a-4719-83cc-f350d8f0de12|0|.0
So I wrote the thing up. Compared with a fairly common alternative, a run of the more type safe and render friendly template took 260 ms to merge 10k iterations vs. 480 ms for the alternative. In addition, the alternative went through almost twice the allocations (meaning more GC would result).
I'm kind of pleased that this is faster than the alternative, but am going to do some more thinking of a better way. A 46% reduction is nice, but it's just under twice as fast. Still, the benefits of a cached template and token/key checking beats the alternative.
Toying with the idea of a strongly typed template leads to a dead end pretty much. Sure, one could dynamically emit a class that exposes the token names as properties, but then how would you bind to these in compile time? since the tokens are NOT known at compile time, the programmer using the class would have to populate a name value pair in some fashion, so that at run time the binding could be made.
//------------------------------------
namespace Nuri.FasterTemplate
{
public interface IFasterTemplate
{
string ID { get; }
void Render(System.IO.TextWriter writer, IRuntimeValues runtimeValues);
IRuntimeValues GetRuntimeValuesPrototype();
}
}
//------------------------------------
namespace Nuri.FasterTemplate
{
public interface IRuntimeValues
{
bool AddValue(string tokenName, string value);
bool AddValues(IEnumerable values);
string[] GetAllowedValues();
IRuntimeValues GetCopy();
bool IsAlowed(string tokenName);
string this[string tokenName] { get; }
string ID { get; }
}
}
//------------------------------------
namespace Nuri.FasterTemplate
{
public static class FasterTempateFactory
{
public static IFasterTemplate GetFasterTemplate(TokenConfiguration config, ref string template)
{
string id = Guid.NewGuid().ToString();
TemplateParts templateParts = processTemplateParts(id, config, ref template);
IRuntimeValues runtimeValuesPrototype = processTokens(id, templateParts);
IFasterTemplate result = new FasterTemplate(id, templateParts, runtimeValuesPrototype);
return result;
}
private static IRuntimeValues processTokens(string id, TemplateParts templateParts)
{
RuntimeValues result = new RuntimeValues(id);
for (int i = 0; i < templateParts.Count; i++)
if (templateParts[i].IsToken)
result[templateParts[i].Value] = string.Empty;
return result;
}
private static TemplateParts processTemplateParts(string id, TokenConfiguration config, ref string template)
{
TemplateParts result = new TemplateParts();
char currentChar;
bool isToken = false;
StringBuilder sbText = new StringBuilder(template.Length / 2);
StringBuilder sbToken = new StringBuilder(64);
for (int idx = 0; idx < template.Length; idx++)
{
currentChar = template[idx];
if (currentChar == config.TokenStartMarker)
{ isToken = true; result.Add(new TemplatePart(sbText.ToString(), false)); sbText.Length = 0; }
else if (currentChar == config.TokenEndMarker) { isToken = false; result.Add(new TemplatePart(sbToken.ToString(), true)); sbToken.Length = 0; } else { if (isToken) sbToken.Append(currentChar); else sbText.Append(currentChar); }
} if (isToken == true) throw new ArgumentException("Template has unclosed token marker"); if (sbText.Length > 0) result.Add(new TemplatePart(sbText.ToString(), false)); return result;
}
}
}
//------------------------------------
namespace Nuri.FasterTemplate
{
internal class FasterTemplate : IFasterTemplate
{
private string _ID;
private TemplateParts _TemplateParts;
private IRuntimeValues _RuntimeValuesPrototype;
internal FasterTemplate(string ID, TemplateParts templateParts, IRuntimeValues runtimeValuesPrototype) { _ID = ID; _TemplateParts = templateParts; _RuntimeValuesPrototype = runtimeValuesPrototype; } public IRuntimeValues GetRuntimeValuesPrototype() { return _RuntimeValuesPrototype.GetCopy(); } public string ID { get { return _ID; } }
public void Render(System.IO.TextWriter writer, IRuntimeValues runtimeValues)
{
if (runtimeValues.ID != this._ID) throw new ArgumentException("The runtime values supplied are not compatible with this template! Ensure you got the runtime values object from the template with ID " + this._ID); for (int i = 0, count = _TemplateParts.Count; i < count; i++)
{
TemplatePart part = _TemplateParts[i]; if (part.IsToken) { writer.Write(runtimeValues[part.Value]); }
else
{
writer.Write(part.Value);
}
}
}
}
}
//------------------------------------
namespace Nuri.FasterTemplate
{
internal class RuntimeValues : Nuri.FasterTemplate.IRuntimeValues
{
private Dictionary<string,string> _AllowedValues;
internal string _ID;
internal RuntimeValues(string ID, int capacity)
{
_AllowedValues = new Dictionary<string, string>(capacity);
_ID = ID;
}
internal RuntimeValues(string ID) : this(ID, 0x10) { }
internal Dictionary<string,string> AllowedValues
{
get { return _AllowedValues; }
}
public string[] GetAllowedValues()
{
string[] result = new string[_AllowedValues.Count];
int i = 0;
foreach (string key in _AllowedValues.Keys)
{ result[i++] = key; }
return result;
}
public bool IsAlowed(string tokenName)
{
return _AllowedValues.ContainsKey(tokenName);
}
public bool AddValue(string tokenName, string value)
{
if (_AllowedValues.ContainsKey(tokenName))
{
_AllowedValues[tokenName] = value;
return true;
}
else
return false;
}
public bool AddValues(IEnumerable values)
{
bool result = true;
foreach (KeyValuePair<string, string> pair in values)
{
result = result && this.AddValue(pair.Key, pair.Value);
}
return result;
}
public string this[string tokenName]
{
get
{
return _AllowedValues[tokenName];
}
internal set
{ _AllowedValues[tokenName] = value; }
}
public IRuntimeValues GetCopy()
{
RuntimeValues result = new RuntimeValues(this._ID, _AllowedValues.Count);
foreach (string key in _AllowedValues.Keys)
result.AllowedValues[key] = string.Empty;
return result;
}
public string ID
{
get { return _ID; }
}
}
}
//------------------------------------
namespace Nuri.FasterTemplate
{
internal struct TemplatePart
{
public bool IsToken;
public string Value;
public TemplatePart(string value, bool isToken)
{
this.Value = value;
this.IsToken = isToken;
}
}
}
//------------------------------------
namespace Nuri.FasterTemplate
{
class TemplateParts : List<TemplatePart> { }
}
//------------------------------------
namespace Nuri.FasterTemplate
{
public struct TokenConfiguration
{
public readonly char TokenStartMarker;
public readonly char TokenEndMarker;
public TokenConfiguration(char tokenStartMarker, char tokenEndMarker)
{
TokenStartMarker = tokenStartMarker;
TokenEndMarker = tokenEndMarker;
}
}
}
03832290-d95e-4c97-a580-0afebc1b609a|0|.0