Tags: , , | Categories: Code Development, General, Generics Posted by nurih on 11/2/2006 9:53 PM | Comments (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"); }
}
}
}
Tags: , , , | Categories: Code Development, Workflow Foundation Posted by nurih on 11/2/2006 4:02 PM | Comments (0)

In developing some workflow stuff, ran across the need to create custom activities that "aggregate" other atomic activities into a larger activity. The idea is that

  1. Programmer codes some atomic activities, exposes appropriate Dependency Properties
  2. Non programmer
    1. Uses these atomics and creates processes using the atomic compiled activities
    2. Saves these composite activities as a new custom activity
    3. Can expose specific parameters for inner activities, but otherwise the activity is "locked" for the end user.
  3. End user
    1. Uses the composite activities to define a workflow and run it.
    2. Can bind runtime parameters to the composite activities.
  4. Runtime picks up an end user's activity and runs it.

 

Gotcha's:

  1. Designer re-hosting is a bit more complex than I would like it to be..
  2. Had to fool around with designer emitted dependency properties "Promote Bindable Properties" and ensure it would do the trick. This is the best way I found so far to expose inner properties of the atomic activities to the "surface" of the composite activities and allow the end user to assign values to them.
  3. Had to add to the compilation a ToolboxItem attribute (the re-hosting examples don't do that, and since the non programmer does NOT have access to the code beside file, you have to add it within the designer compilation of the designer activity. The exact magic incantations are:
    	////////////////////// add these lines to the workflow loader:
    	CodeAttributeDeclaration attrdecl = new CodeAttributeDeclaration(
    	"System.ComponentModel.ToolboxItem",
    	new CodeAttributeArgument(new CodePrimitiveExpression(true))
    	);
    	ctd.CustomAttributes.Add(attrdecl);
    	CodeCommentStatement nurisComment = new CodeCommentStatement(
    	new CodeComment("ToolboxItem decoration should do the trick.."));
    	ctd.Comments.Add(nurisComment);
    	////////////////////// end added lines
    	
Tags: , , , , | Categories: Code Development, General, Workflow Foundation Posted by nurih on 11/2/2006 3:40 PM | Comments (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; 
} 
} 
}