I use PostSharp aspect below to validate Property of the class.
[ProtoContract]
public sealed class Web2Image : WebEntity
{
[ProtoMember(1009), Validator.Collection(Data = new[] { "jpg", "bmp", "png", "tiff" })]
public override string OutputFormat { get; set; }
}
The property OutputFormat is validated on first property access but Validation is executed and second and third time when property accessed in the code.
I would like to limit Aspect execution only once per class instance for my property. How to do that?
public class Validator
{
[Serializable]
[Collection(AttributeExclude = true)]
[MulticastAttributeUsage(MulticastTargets.Property)]
public class Collection : LocationInterceptionAspect
{
public string[] Data;
public override void OnGetValue(LocationInterceptionArgs args)
{
SiAuto.Main.LogObject("FieldAccessEventArgs " + Reflection.AssemblyHelper.EntryAssembly, args);
/* SiAuto.Main.LogObject("FieldAccessEventArgs " + Reflection.AssemblyHelper.EntryAssembly, args.Binding.ToString());*/
args.ProceedGetValue();
if (args.Value == null)
{
args.Value = Data[0];
args.ProceedSetValue();
}
foreach (var s in Data)
{
if (args.Value.ToString().ToLower() == s.ToLower())
return;
}
throw new EngineException(string.Format("Value \"{0}\" is not correct. {1} parameter can accept only these values {2}", args.Value, args.LocationName, string.Join(",", Data)));
}
}
}
You will need to implement IInstanceScopedAspect. See http://www.sharpcrafters.com/blog/post/Day-9-Aspect-Lifetime-Scope-Part-1.aspx and http://www.sharpcrafters.com/blog/post/Day-10-Aspect-Lifetime-Scope-Part-2.aspx for more about the lifetime and scope of aspects including how to implement IInstanceScopedAspect.
That will get you the per instance aspect (because right now it's once per type).
As far as the checking, you can set a switch (if true, exit otherwise, do check) or check if it's null (or some other initial value).
Related
I'm using the adapter support in Autofac to convert multiple types to a desired type. I also want to preserve the keys/names/metadata attached to the adapter input types, so that they exist with the same values on the adapter output types - this is needed for using IIndex<,> to resolve instances by name.
I can't figure out how to propagate the keys/names/metadata through the adapter function, since the adapter function runs during component construction, and the metadata needs to be propagated when the container is built.
Here's an example xunit test, which fails:
/// <summary>
/// Unit test to figure out how to propagate keys through adapters.
/// </summary>
public sealed class AutofacAdapterTest
{
public class A
{
public A(string key)
{
Key = key;
}
public string Key { get; private set; }
}
public class B
{
public B(string name)
{
Name = name;
}
public string Name { get; private set; }
}
public class C : B
{
public C(string name)
: base(name)
{}
}
public class LookerUpper
{
private readonly IIndex<string, B> _bIndex;
public LookerUpper(IIndex<string, B> bIndex)
{
_bIndex = bIndex;
}
public B LookupByName(string name)
{
return _bIndex[name];
}
}
[Fact]
public void TestPropagateKeysThroughAdapters()
{
var builder = new ContainerBuilder();
// Register named types
builder.RegisterType<A>().Named<A>("A").WithParameter("key", "A");
builder.RegisterType<B>().Named<B>("B").WithParameter("name", "B");
builder.RegisterType<C>().Named<C>("C").Named<B>("C").WithParameter("name", "C");
// Adapter to convert an A to a B, since it's not a subclass
builder.RegisterAdapter<A, B>((c, a) => new B(a.Key));
// Register LookerUpper, which is the only top-level type that needs to be autowired
builder.RegisterType<LookerUpper>();
var container = builder.Build();
var lookerUpper = container.Resolve<LookerUpper>();
// Test expected results
Assert.Equal("A", lookerUpper.LookupByName("A").Name);
Assert.IsType<B>(lookerUpper.LookupByName("A")); // A should have been adapted to a B
Assert.Equal("B", lookerUpper.LookupByName("B").Name);
Assert.IsType<B>(lookerUpper.LookupByName("B"));
Assert.Equal("C", lookerUpper.LookupByName("C").Name);
Assert.IsType<C>(lookerUpper.LookupByName("C"));
Assert.Throws<ComponentNotRegisteredException>(() => lookerUpper.LookupByName("D"));
}
}
The statement lookerUpper.LookupByName("A") fails with a ComponentNotRegisteredException, because the name value "A" is not propagated through the adapter function (which adapts A -> B ). If the first two lines of Asserts are commented out, the rest of the test works as expected.
I found a workable solution to this problem by using Autofac Metadata instead of Autofac keys or names. For the call to RegisterAdapter<TFrom, TTo>(Func<TFrom,TTo>), metadata is propagated from the IComponentRegistration for TFrom to the IComponentRegistration for TTo; however the keys/names are not propagated. The omission of keys may be a bug or by design, I'll file a bug with autofac to figure out which is the case and follow up.
The unfortunate part about using metadata is I can't use an IIndex<string, B> constructor parameter, so I had to use an IEnumerable<Meta<Lazy<B>>> parameter and create my own dictionary of string -> Lazy<B> to provide similiar functionality to IIndex. Here's the code that works:
/// <summary>
/// Unit test to figure out how to propagate keys through adapters.
/// </summary>
public sealed class AutofacAdapterTest
{
internal const string LookupKey = "lookup";
public class A
{
public A(string key)
{
Key = key;
}
public string Key { get; private set; }
}
public class B
{
public B(string name)
{
Name = name;
}
public string Name { get; private set; }
}
public class C : B
{
public C(string name)
: base(name)
{}
}
public class LookerUpper
{
private readonly IDictionary<string, Lazy<B>> _bLookup;
public LookerUpper(IEnumerable<Meta<Lazy<B>>> bMetas)
{
_bLookup = bMetas.ToDictionary(meta => meta.Metadata[LookupKey].ToString(), meta => meta.Value);
}
public B LookupByName(string name)
{
return _bLookup[name].Value;
}
}
[Fact]
public void TestPropagateKeysThroughAdapters()
{
var builder = new ContainerBuilder();
// Register types that will be looked up; attach metadata for the lookup key
builder.Register((c) => new A("A")).WithMetadata(LookupKey, "A");
builder.Register((c) => new B("B")).WithMetadata(LookupKey, "B");
builder.Register((c) => new C("C")).AsSelf().As<B>().WithMetadata(LookupKey, "C");
// Adapter to convert an A to a B, since it's not a subclass
builder.RegisterAdapter<A, B>((c, a) => new B(a.Key));
// Register LookerUpper, which is the only top-level type that needs to be autowired
builder.RegisterType<LookerUpper>();
var container = builder.Build();
var lookerUpper = container.Resolve<LookerUpper>();
// Test expected results
Assert.Equal("A", lookerUpper.LookupByName("A").Name);
Assert.IsType<B>(lookerUpper.LookupByName("A")); // A should have been adapted to a B
Assert.Equal("B", lookerUpper.LookupByName("B").Name);
Assert.IsType<B>(lookerUpper.LookupByName("B"));
Assert.Equal("C", lookerUpper.LookupByName("C").Name);
Assert.IsType<C>(lookerUpper.LookupByName("C"));
Assert.Throws<KeyNotFoundException>(() => lookerUpper.LookupByName("D"));
}
}
It should also be possible to create an IRegistrationSource and some extension methods that extend what is done in RegisterAdapter<TFrom, TTo>, such that the keys in TFrom are propagated to TTo - that would be an ideal solution, but potentially more work to maintain, so I'll probably stick with this.
It was fixed in Autofac version 3.5.1.
Link to the bug
Link to the fix
I have this implementation of versioned property
public class VersionedProperty<T>: Dictionary<int, T>, IParentEntityTracker //where T:IComparable<T>
{
[BsonIgnore]
public IVersionableEntity ParentEntity { get; set; }
public VersionedProperty()
{
}
public VersionedProperty(IVersionableEntity parentEntity)
{
ParentEntity = parentEntity;
}
public T Value
{
set
{
var curVal = Value;
if (EqualityComparer<T>.Default.Equals(curVal, value))
return;
ParentEntity.AtLeastOneVersionedPropertyModified = true;
this[ParentEntity.Version + 1] = value;
}
get
{
var key = ParentEntity.Version + (ParentEntity.AtLeastOneVersionedPropertyModified ? 1 : 0);
var keys = Keys.Where(k => k<=key).ToList();
if (!keys.Any())
return default(T);
var max = keys.Max();
T res;
TryGetValue(max, out res);
return res;
}
}
}
Initially I created some property in the document as not versioned. Let say
public class Product
{
public Decimal Price{get;set;}
}
after some time I realized that I did mistake and I should use versioned property
public class Product
{
public VersionedProperty<Decimal> Price{get;set;}
}
What I would like is that old value in the existing document automatically deserialized into this versioned property to avoid writing update queries on the collection.
Is it possible somehow interfere with the process of deserializing?
May be this can help: I use postsharp to create empty instance of versioned property for autoproperties. My postsharp aspect also assigns reference of a parent entity to ParentEntity property of that newly created empty instance of the versioned property.
I was thinking to generate EntityTypeConfiguration dynamically from run time and i don't want any EF dependency in Models[That is why i avoid Data Annotation].
So I declare a custom attribute(or can be any configuration file later on)
[AttributeUsage(AttributeTargets.Property, AllowMultiple=true )]
public class PersistableMemberAttribute : Attribute
{
public bool Iskey;
public bool IsRequired;
public bool IsIgnored;
public bool IsMany;
public string HasForeignKey;
public bool PropertyIsRequired;
public bool PropertyIsOptional;
}
And here is one of my Models is look like:
public class Blog
{
[PersistableMember(Iskey=true)]
public Guid BlogId { get; set; }
[PersistableMember(PropertyIsRequired = true)]
public string Name { get; set; }
public string Url { get; set; }
[PersistableMember(IsIgnored=true)]
public int Rating { get; set; }
[PersistableMember(IsMany =true)]
public ICollection<Post> Posts { get; set; }
}
Now I am going to write a generic EntityTypeConfiguration , which will create the configuration dynamically on run time based on the attribute values :
public class GenericEntityConfiguration<T> : EntityTypeConfiguration<T> where T : class
{
public GenericEntityConfiguration()
{
var members = typeof(T).GetProperties();
if (null != members)
{
foreach (var property in members)
{
var attrb= property.GetCustomAttributes(typeof( PersistableMemberAttribute ),false).OfType<PersistableMemberAttribute>();
if (attrb != null && attrb.Count() > 0)
{
foreach (var memberAttributute in attrb)
{
if (memberAttributute.Iskey || memberAttributute.IsIgnored)
{
var entityMethod = this.GetType().GetMethod("Setkey");
entityMethod.MakeGenericMethod(property.PropertyType)
.Invoke(this, new object[] { property, memberAttributute });
}
if (memberAttributute.IsRequired)
{
var entityMethod = this.GetType().GetMethod("SetRequired");
entityMethod.MakeGenericMethod(property.PropertyType)
.Invoke(this, new object[] { property, memberAttributute });
}
if (memberAttributute.PropertyIsRequired || memberAttributute.PropertyIsOptional)
{
var entityMethod = this.GetType().GetMethod("SetPropertyConfiguration");
entityMethod.MakeGenericMethod(property.PropertyType)
.Invoke(this, new object[] { property, memberAttributute });
}
}
}
}
}
}
public void SetPropertyConfiguration<TResult>(PropertyInfo propertyInfo, PersistableMemberAttribute attribute)
{
var functorParam = Expression.Parameter(typeof(T));
var lambda = Expression.Lambda(
Expression.Property(functorParam, propertyInfo)
, functorParam);
if (attribute.PropertyIsRequired)
{
this.Property<TResult>((Expression<Func<T, TResult>>)lambda).IsRequired();
}
if (attribute.PropertyIsOptional)
{
this.Property<TResult>((Expression<Func<T, TResult>>)lambda).IsOptional();
}
}
public void Setkey<TResult>(PropertyInfo propertyInfo, PersistableMemberAttribute attribute)
{
var functorParam = Expression.Parameter(typeof(T));
var lambda = Expression.Lambda(
Expression.Property(functorParam, propertyInfo)
, functorParam);
if (attribute.Iskey)
{
this.HasKey<TResult>((Expression<Func<T,TResult>>)lambda);
}
if (attribute.IsIgnored)
{
this.Ignore<TResult>((Expression<Func<T, TResult>>)lambda);
}
}
public void SetRequired<TResult>(PropertyInfo propertyInfo, PersistableMemberAttribute attribute) where TResult : class
{
var functorParam = Expression.Parameter(typeof(T));
var lambda = Expression.Lambda(
Expression.Property(functorParam, propertyInfo)
, functorParam);
if (attribute.IsRequired)
{
this.HasRequired<TResult>((Expression<Func<T, TResult>>)lambda);
}
}
}
But i got the compilation error of
Error 1 The type 'TResult' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'System.Data.Entity.ModelConfiguration.Configuration.StructuralTypeConfiguration.Property(System.Linq.Expressions.Expression>)' D:\R&D\UpdateStorePOC\UpdateStorePOC\Data\GenericEntityConfiguration.cs 63 17 UpdateStorePOC
which for these two statements:
this.Property<TResult>((Expression<Func<T, TResult>>)lambda).IsRequired();
this.Property<TResult>((Expression<Func<T, TResult>>)lambda).IsOptional();
that means that I need to put a constraint on my method to restrict it to a value type. In C#, this is done with the ‘struct’ keyword.
public void SetPropertyConfiguration<TResult>(PropertyInfo propertyInfo, PersistableMemberAttribute attribute) Where TResult : struct
But Its not the solution since my property type can be a class e.g string or int, bool double, etc . So it is not at all clear that I can send them into this method. Please help me to solve this issue whether there is any other way to do it.
I don't want any EF dependency in models.
With fluent mapping you're almost there and you won't come any closer. Your attributes, even though intended to be moved to a configuration file, don't make your model any more free of any EF footprint.1 Worse, they only add a second mapping layer (if you like) between your model and EF's mapping. I only see drawbacks:
You still have to maintain meta data for your model, probably not any less than regular fluent mapping and (probably) in awkward manually edited XML without compile-time checking.
You will keep expanding your code to cover cases that EF's mapping covers but yours doesn't yet.2 So it's a waste of energy: in the end you'll basically have rewritten EF's mapping methods.
You'll have to keep your fingers crossed when you want to upgrade EF.
With bugs/problems you're on your own: hard to get support from the community.
So my answer to your question help me to solve this issue would be: use fluent mapping out of the box. Keep it simple.
1 For example, you would still have to use the virtual modifier to enable proxies for lazy loading.
2 Like support for inheritance, unmapped foreign keys, max length, db data type, ... this could go on for a while.
I'd like to define in class declaration which items are index, something like:
public class MyClass {
public int SomeNum { get; set; }
[THISISANINDEX]
public string SomeProperty { get; set; }
}
so to have the same effect as ensureIndex("SomeProperty")
Is this possible?
I think this is a nice idea, but you have to do this yourself, there's no built-in support for it. If you have an access layer you can do it in there. You'd need an attribute class, something like this;
public enum IndexConstraints
{
Normal = 0x00000001, // Ascending, non-indexed
Descending = 0x00000010,
Unique = 0x00000100,
Sparse = 0x00001000, // allows nulls in the indexed fields
}
// Applied to a member
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class EnsureIndexAttribute : EnsureIndexes
{
public EnsureIndex(IndexConstraints ic = IndexConstraints.Normal) : base(ic) { }
}
// Applied to a class
[AttributeUsage(AttributeTargets.Class)]
public class EnsureIndexesAttribute : Attribute
{
public bool Descending { get; private set; }
public bool Unique { get; private set; }
public bool Sparse { get; private set; }
public string[] Keys { get; private set; }
public EnsureIndexes(params string[] keys) : this(IndexConstraints.Normal, keys) {}
public EnsureIndexes(IndexConstraints ic, params string[] keys)
{
this.Descending = ((ic & IndexConstraints.Descending) != 0);
this.Unique = ((ic & IndexConstraints.Unique) != 0); ;
this.Sparse = ((ic & IndexConstraints.Sparse) != 0); ;
this.Keys = keys;
}
}//class EnsureIndexes
You could then apply attributes at either the class or member level as follows. I found that adding at member level was less likely to get out of sync with the schema compared to adding at the class level. You need to make sure of course that you get the actual element name as opposed to the C# member name;
[CollectionName("People")]
//[EnsureIndexes("k")]// doing it here would allow for multi-key configs
public class Person
{
[BsonElement("k")] // name mapping in the DB schema
[BsonIgnoreIfNull]
[EnsureIndex(IndexConstraints.Unique|IndexConstraints.Sparse)] // name is implicit here
public string userId{ get; protected set; }
// other properties go here
}
and then in your DB access implementation (or repository), you need something like this;
private void AssureIndexesNotInlinable()
{
// We can only index a collection if there's at least one element, otherwise it does nothing
if (this.collection.Count() > 0)
{
// Check for EnsureIndex Attribute
var theClass = typeof(T);
// Walk the members of the class to see if there are any directly attached index directives
foreach (var m in theClass.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
{
List<string> elementNameOverride = new List<string>(1);
EnsureIndexes indexAttr = null;
// For each members attribs
foreach (Attribute attr in m.GetCustomAttributes())
{
if (attr.GetType() == typeof(EnsureIndex))
indexAttr = (EnsureIndex)attr;
if (attr.GetType() == typeof(RepoElementAttribute))
elementNameOverride.Add(((RepoElementAttribute)attr).ElementName);
if ((indexAttr != null) && (elementNameOverride.Count != 0))
break;
}
// Index
if (indexAttr != null)
{
if (elementNameOverride.Count() > 0)
EnsureIndexesAsDeclared(indexAttr, elementNameOverride);
else
EnsureIndexesAsDeclared(indexAttr);
}
}
// Walk the atributes on the class itself. WARNING: We don't validate the member names here, we just create the indexes
// so if you create a unique index and don't have a field to match you'll get an exception as you try to add the second
// item with a null value on that key
foreach (Attribute attr in theClass.GetCustomAttributes(true))
{
if (attr.GetType() == typeof(EnsureIndexes))
EnsureIndexesAsDeclared((EnsureIndexes)attr);
}//foreach
}//if this.collection.count
}//AssureIndexesNotInlinable()
EnsureIndexes then looks like this;
private void EnsureIndexesAsDeclared(EnsureIndexes attr, List<string> indexFields = null)
{
var eia = attr as EnsureIndexes;
if (indexFields == null)
indexFields = eia.Keys.ToList();
// use driver specific methods to actually create this index on the collection
var db = GetRepositoryManager(); // if you have a repository or some other method of your own
db.EnsureIndexes(indexFields, attr.Descending, attr.Unique, attr.Sparse);
}//EnsureIndexes()
Note that you'll place this after each and every update because if you forget somewhere your indexes may not get created. It's important to ensure therefore that you optimise the call so that it returns quickly if there's no indexing to do before going through all that reflection code. Ideally, you'd do this just once, or at the very least, once per application startup. So one way would be to use a static flag to track whether you've already done so, and you'd need additional lock protection around that, but over-simplistically, it looks something like this;
void AssureIndexes()
{
if (_requiresIndexing)
AssureIndexesInit();
}
So that's the method you'll want in each and every DB update you make, which, if you're lucky would get inlined by the JIT optimizer as well.
See below for a naive implementation which could do with some brains to take the indexing advice from the MongoDb documentation into consideration. Creating indexes based on queries used within the application instead of adding custom attributes to properties might be another option.
using System;
using System.Reflection;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using NUnit.Framework;
using SharpTestsEx;
namespace Mongeek
{
[TestFixture]
class TestDecorateToEnsureIndex
{
[Test]
public void ShouldIndexPropertyWithEnsureIndexAttribute()
{
var server = MongoServer.Create("mongodb://localhost");
var db = server.GetDatabase("IndexTest");
var boatCollection = db.GetCollection<Boat>("Boats");
boatCollection.DropAllIndexes();
var indexer = new Indexer();
indexer.EnsureThat(boatCollection).HasIndexesNeededBy<Boat>();
boatCollection.IndexExists(new[] { "Name" }).Should().Be.True();
}
}
internal class Indexer
{
private MongoCollection _mongoCollection;
public Indexer EnsureThat(MongoCollection mongoCollection)
{
_mongoCollection = mongoCollection;
return this;
}
public Indexer HasIndexesNeededBy<T>()
{
Type t = typeof (T);
foreach(PropertyInfo prop in t.GetProperties() )
{
if (Attribute.IsDefined(prop, typeof (EnsureIndexAttribute)))
{
_mongoCollection.EnsureIndex(new[] {prop.Name});
}
}
return this;
}
}
internal class Boat
{
public Boat(Guid id)
{
Id = id;
}
[BsonId]
public Guid Id { get; private set; }
public int Length { get; set; }
[EnsureIndex]
public string Name { get; set; }
}
internal class EnsureIndexAttribute : Attribute
{
}
}
I'm trying to apply the MVVM design pattern to a diagramming application. In this application there are different items (for example a rectangle, a circle,...). I would like to save the item type as an enum in my model.
In my modelview I made a class for every item type (rectangleViewMode, circleViewMode,...).
On my view I apply a data template to the type, so it renders like a circle, or like a rectangle.
The problem is...how can I convert the enum in my model to the requiered xxxViewMode? I have a lot of types and I would like an automatic conversion.
I'm new to MVVM and maybe there is a better approach...so better solutions are welcome! :)
Thank you very much
I read your question a little differently to the other answerers, i don't believe you are just looking for a way to bind an enum to a combo, i think you are looking for a way to relate an enum value to an object type. If i got this wrong then stop reading now :)
First up: I'm not sure that saving the shape types as an enumeration (or even relating the shape to an enumeration) is very scalable. Read on, and i'll explain towards the end.
To relate an item type to an enum, just have the item return the appropriate enum value via a property:
public CircleViewMode
{
public ShapeType Shape { get { return ShapeType.Circle; }}
}
public enum ShapeType
{
Circle,
Square,
Rectangle,
Triangle,
FancyShape1,
FancyShape2
}
This means that you don't have to employ a converter or another translator mechanism. If you want to then populate a bunch of these into a combo then it is quite simple - check this following sample and insert breakpoints at the appropriate spot to see how it works.
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
new Example().Run();
Console.ReadKey();
}
}
public class Example : INotifyPropertyChanged
{
public void Run()
{
var availableShapes = AllMyShapes.Where(x => x.NumberOfSides == 4);
AvailableShapes = new List<KeyValuePair<string, Type>>
(from Shape s in availableShapes
select new KeyValuePair<string, Type>(
Enum.GetName(typeof(ShapeType), s.ShapeType)
,s.GetType()
));
//at this point any combobox you have bound to the AvailableShapes property will now carry a new list of shapes
}
public List<Shape> AllMyShapes
{
get
{
return new List<Shape>() { new Circle(){NumberOfSides=1, ShapeType=ShapeType.Circle}
,new Square(){NumberOfSides=4, ShapeType=ShapeType.Square}
,new Rectangle(){NumberOfSides=4, ShapeType=ShapeType.Rectangle}
,new Triangle(){NumberOfSides=3, ShapeType=ShapeType.Triangle}
,new FancyShape1(){NumberOfSides=10, ShapeType=ShapeType.FancyShape1}
,new FancyShape2(){NumberOfSides=30, ShapeType=ShapeType.FancyShape2}
};
}
}
public List<KeyValuePair<string, Type>> AvailableShapes
{
get { return _availableShapes; }
protected set
{
_availableShapes = value;
}
}
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private List<KeyValuePair<string, Type>> _availableShapes;
public event PropertyChangedEventHandler PropertyChanged;
}
public abstract class Shape
{
public int NumberOfSides { get; set; }
public ShapeType ShapeType { get; set; }
}
public class Square : Shape { }
public class Rectangle : Shape { }
public class Triangle : Shape { }
public class Circle : Shape { }
public class FancyShape1 : Shape { }
public class FancyShape2 : Shape { }
public enum ShapeType
{
Circle,
Square,
Rectangle,
Triangle,
FancyShape1,
FancyShape2
}
}
With this approach you will have a combobox with nice human readable shape names in it, and you can instantly get the actual shape type of the selected item. It would be a trivial task to turn the class Example into an abstract base ViewModel, any ViewModel you then derive from it will have the AvailableShapes property.
But back to my original point of scalability - as you increase the shape types you also need to update the enumeration. This could be problematic if you ship libraries of new shapes or allow users to create their own. You may be better off saving it as myShape.GetType().ToString(), which returns a string value that can then be used to recreate the an instance of the object using reflection. In the above example of showing the items in a combo, you could just get a List<Type> of the available shapes and use a converter to produce a nice human readable name from the shape type (using a string resource file, eliminating the enumeration altogether).
Depending on your needs, you could use a converter class:
(stolen from How to bind RadioButtons to an enum?)
public class EnumBooleanConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string parameterString = parameter as string;
if (parameterString == null)
return DependencyProperty.UnsetValue;
if (Enum.IsDefined(value.GetType(), value) == false)
return DependencyProperty.UnsetValue;
object parameterValue = Enum.Parse(value.GetType(), parameterString);
return parameterValue.Equals(value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string parameterString = parameter as string;
if (parameterString == null)
return DependencyProperty.UnsetValue;
return Enum.Parse(targetType, parameterString);
}
#endregion
}