|
Okay so a generic interface is, at its most basic level, an interface that has a generic element to it. In other words, the signature of the interface means that it can be used for different types. Consider this interface:
public interface IPersist<T>
{
void LoadLayout(T instance);
void SaveLayout(T instance);
} Suppose we have a class that looks like this:
public class MyModel
{
public string Name { get; set; }
public string FriendlyName { get; set; }
public DateTime LastLogin { get; set; }
} Now, we want to write a class that performs the persistence for this class. Let's write it like this:
public class MyModelPersistence : IPersist<MyModel>
{
public void LoadLayout(MyModel instance)
{
}
public void SaveLayout(MyModel instance)
{
}
} A common use of generic interfaces is in providing the interface definitions for generic collections such as List<T> .
Now, where things get really interesting is when it comes to covariant and contravariant interfaces. I will direct you to the discussion here[^]. The discussion around the zoo is an excellent example of using co and contra variance.
Similar to a generic interface, a generic delegate is one that uses the generic parameter to create a strong type (basically, by giving it a Type, you remove the need to use object as the delegate type, thus removing the need to perform boxing/unboxing to get the value of the parameter).
|
|
|
|
|
I hate you yoda! that simple description of generic interfaces makes we want to revisit my data layer and see if it is a viable change to the approach I use now
otherwise +5 for a simple explination
Every day, thousands of innocent plants are killed by vegetarians.
Help end the violence EAT BACON
|
|
|
|
|
My way of doing things changed to thinking like this once I really got into SOLID as a discipline.
|
|
|
|
|
Pete O'Hanlon wrote: ... once I really got into SOLID as a discipline Hi Pete, Do you ever have flash-backs where you re-live being amorphous ? I know I do; soy-milk helps me with that. Maybe my problem is I am just too "at home" in amorphous.
I'm going to dare post an alternative take on generic interface on this thread based on your example, and I'd be very keen to have your feedback ... since I trust you more than I trust myself (with code).
I'm curious if you see any "inherent tension" between the "I" principle in SOLID: "many client-specific interfaces are better than one general-purpose interface" (Martin), and the use of generic interfaces. I do not claim to "grasp" the SOLID principles in depth, but I am trying to increase my understanding of them.
cheers, Bill
«To kill an error's as good a service, sometimes better than, establishing new truth or fact.» Charles Darwin in "Prospero's Precepts"
modified 14-Apr-15 13:42pm.
|
|
|
|
|
I loved being amorphous. Providing my own gaseous cloud was one of the joys of single life. BillWoodruff wrote: 'm going to dare post an alternative take on generic interface on this thread based on your example Go for it. My example is merely one way of doing this, based on typing in the editor.
|
|
|
|
|
Thanks Pete O'Hanlon for your simple explanation.
|
|
|
|
|
|
This is an "alternate take" on the code example using a generic interface provided on this thread by Pete O'Hanlon. It uses a pattern I've used before which is slightly different than the in Pete's example; of course I do not claim this is any "better" than Pete's example !
I've made sure this example actually works by testing it. 'DataContract/DataMember is used for serialization/de-serialization. I'd really appreciate any feedback on this code vis-a-vis its differences with Pete's implementation.
using System;
using System.IO;
using System.Runtime.Serialization;
namespace YourNameSpace
{
public interface IPersist<T> where T : class
{
void LoadLayout();
void SaveLayout();
}
[DataContract]
public class MyModel : IPersist<MyModel>
{
private readonly string FilePath;
public MyModel(string name, string fname, string filepath)
{
Name = name;
FriendlyName = fname;
FilePath = filepath;
LastLogin = DateTime.Now;
}
[DataMember]
public string Name { private set; get; }
[DataMember]
public string FriendlyName { private set; get; }
[DataMember]
public DateTime LastLogin { private set; get; }
public void LoadLayout()
{
if (!File.Exists(FilePath)) throw new FileNotFoundException("Invalid file path");
MyModel temp;
try
{
temp = Persistence<MyModel>.LoadLayout(FilePath);
}
catch (Exception ex)
{
throw new InvalidDataContractException(string.Format("Failed to read data file: {0}", ex.Message),
ex.InnerException);
}
if (temp == null) throw new InvalidDataContractException("no data error");
Name = temp.Name;
FriendlyName = temp.FriendlyName;
LastLogin = temp.LastLogin;
}
public void SaveLayout()
{
if (!File.Exists(FilePath)) throw new FileNotFoundException("Invalid file path");
try
{
Persistence<MyModel>.SaveLayout(FilePath, this);
}
catch (Exception ex)
{
throw new InvalidDataContractException(string.Format("Failed to save to data file: {0}", ex.Message),
ex.InnerException);
}
}
}
internal static class Persistence<T> where T : class
{
private static DataContractSerializer serializer;
public static T LoadLayout(string filepath)
{
serializer = new DataContractSerializer(typeof (T));
Console.WriteLine("Loading");
T LoadedData = null;
using (var reader = new FileStream(filepath, FileMode.Open, FileAccess.Read))
{
LoadedData = serializer.ReadObject(reader) as T;
}
return LoadedData;
}
public static void SaveLayout(string filepath, T instance)
{
serializer = new DataContractSerializer(typeof (T));
Console.WriteLine("Saving");
using (var writer = new FileStream(filepath, FileMode.Create, FileAccess.Write))
{
serializer.WriteObject(writer, instance);
}
}
}
}
«To kill an error's as good a service, sometimes better than, establishing new truth or fact.» Charles Darwin in "Prospero's Precepts"
modified 15-Apr-15 13:03pm.
|
|
|
|
|
You've added a generic type parameter to the IPersist<T> interface, but you don't seem to use it anywhere. You'd get the same result with a non-generic interface.
The serializer field on your Persistence<T> class should be a local variable in both methods. There's no need to expose it to the outside world, and your current implementation is not thread-safe.
You've also missed the set; (or the body of the get; ) on the FilePath auto-property.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Hi Richard, thanks for your feedback !Richard Deeming wrote: You've added a generic type parameter ... but you don't seem to use it anywher "Yes" in the sense that IPersist could be defined without the generic parameter and still "work;" "no" in the sense that constraining IPersist using 'Where does require generic declaration.Richard Deeming wrote: serializer field ... should be a local variable in both methods. Spot on !Richard Deeming wrote: missed the set; (or the body of the get; ) on the FilePath auto-property. That syntax is certainly legal/compilable in .NET 4.5. An "automatic" property with only a 'getter is de facto readonly. If you think that's a code-smell, I'd be inclined to accept your view. There are those who believe strongly that all automatic properties are "suspect: [^].
At this point, I am not really sure my code example demonstrated the "true virtue" of a "generic interface," but I am very happy to get the chance to learn from you, Pete, and Eddy !
«To kill an error's as good a service, sometimes better than, establishing new truth or fact.» Charles Darwin in "Prospero's Precepts"
|
|
|
|
|
BillWoodruff wrote: That syntax is certainly legal/compilable in .NET 4.5. An "automatic" property with only a 'getter is de facto readonly.
Not in .NET 4.5 - did you mean C# 6 / .NET 4.6 / VS2015?
'FilePath.get' must declare a body because it is not marked abstract or extern. Automatically implemented properties must define both get and set accessors.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Okay ... should have known ... you are right !
I whipped up that sample code in VS 2015 CTP6, but it is compiled with FrameWork 4.5
No compile-time warning, no error message: it works !
I've checked to see if any warning potentially activated by ReSharper 9.1 is turned off, but that's not the case. I've got treat all warnings as errors turned on in VS' 'Build parameter options, and the highest level warning set (4), also.
So, there's a difference in compiler behavior in 2015 CTP 6 ? ... mmm ...
cheers, Bill
«To kill an error's as good a service, sometimes better than, establishing new truth or fact.» Charles Darwin in "Prospero's Precepts"
|
|
|
|
|
Hi Bill,
it's a C# 6.0 feature, readonly auto-properties. Like readonly fields, they can be initialized where they're declared or in the constructor. Here's a nice write-up from Jon Skeet: C# 6 in action[^]
Thank you guys for this interesting thread
cheers, Sascha
|
|
|
|
|
As well as the comments that Richard has made, the fact that MyModel is responsible for also loading MyModel from persistence violates single responsibility if we take the concept here that MyModel represents a data entity of some description. This is why I had the idea of the model separate to the item that's loading the model. One of the main reasons I like to do this is because this clear separation makes things a lot easier for me to test. I can simply mock an interface and do a test on the code that's calling IPersist... to verify that it has made the call (using a mocking framework).
|
|
|
|
|
Hi Pete, I am delighted to have feedback from you !Pete O'Hanlon wrote: the fact that MyModel is responsible for also loading MyModel from persistence violates single responsibility This may be semantic hair-splitting, but do you consider initializing, or "refreshing," the properties of an instance of 'MyModel from an external data file "loading" ?
One question about your code: while having an instance of 'MyModel seems logically required in the 'SaveLayout code, the fact your 'LoadLayout code method is a void-return method means that you would be ... I'm having trouble articulating here ... "refreshing" the field values of the passed-in instance rather than making use of a new instance of 'MyModel ? Sorry if that's unclear; it's now 5:32AM here after a long, sleepless, hot tropical night punctuated by the heavy-ordnance fireworks of Thais celebrating the five-day solar New Year rite-of-passage, Songkraan
I wish I had the background/depth to appreciate your comments on mocking and testing.
thanks, Bill
«To kill an error's as good a service, sometimes better than, establishing new truth or fact.» Charles Darwin in "Prospero's Precepts"
|
|
|
|
|
Hey Bill - glad you liked the comments. Okay, so about the use of LoadLayout and SaveLayout, passing in the underlying model. Strictly speaking, I would normally code up the model so that it really looked like this:
public interface IMyModel
{
string Name { get; set; }
string FriendlyName { get; set; }
DateTime LastLogin { get; set; }
}
public class MyModel : IMyModel
{
public string Name { get; set; }
public string FriendlyName { get; set; }
public DateTime LastLogin { get; set; }
} I then use IoC to create the instance of MyModel for me, and this would be passed in to the layout class like so:
public class MyModelPersistence : IPersist<IMyModel>
{
public void LoadLayout(IMyModel instance)
{
}
public void SaveLayout(IMyModel instance)
{
}
} Note: I didn't put this in my original example because I didn't want to confuse people into thinking that a simple interface was the same as a generic interface. When you see code like this though, you can see that it's incredibly trivial to mock the underlying code so that you can write tests like this:
private IMyModel instance;
[TestInitialize]
public void Setup()
{
instance = Mock.Create<IMyModel>();
instance.Name = "Bob";
instance.FriendlyName = "Extreme CP Overlord";
Mock.Arrange(()=> instance.LastLogin).Returns(GetFixedDateTimeForTest());
}
[Test]
public void WhenValidModelPassedIn_ToLoadLayout_NameIsSet()
{
MyModelPersistence persistence = new MyModelPersistence();
persistence.LoadLayout(instance);
Assert.AreEqual(instance.Name);
} There you can see that we are creating a mocked version of IMyModel that we can use to test how our code works. For such a trivial object, I normally wouldn't bother with this level of mocking and would use the class itself, but if IMyModel was more complex in behaviour then this can become a necessity.
So, why am I not returning an instance of IMyModel from the LoadLayout? It's not the responsibility of persistence to instantiate the object. In other words, we would be violating SRP by doing this - instead, let's use IoC to instantiate our instance elsewhere and pass this in to the persistence method for hydration of the properties. That way, we are keeping a clear delineation between what each layer of code should be doing. The only thing you have to watch out for here is that you need to be very clear about the lifetime of your objects, and you will end up with lots of small classes, each doing one thing. This isn't a bad thing, but you need to be really, really clear about how you name things to make your life easy later on. In other words, try to match your interface name to your class name wherever possible.
You're right that it's not, strictly speaking, loading the properties, but the intent behind my example was to show that we were loading the layout for the model.
A final note - the sad thing is that I have typed my examples entirely here in the CP editor. When you take this approach to breaking your code up like this for a while, it becomes second nature and you find that it's easy to type up even into Notepad.
|
|
|
|
|
BillWoodruff wrote: I've made sure this example actually works by testing it. I got the same compiler-error as Richard
Bonus points for your exception-handling; I use MSDN to see which exceptions I can expect, and they're often listed nicely. Makes life much easier if you can inherit a class and throw exact the same exceptions, knowing that there'll be known exception-handling routines you did not break. Must have missed passing the exception as a second argument to pass it as an inner-exception.
This amount of advice on code that actually looks impressive is why people dislike code-reviews
Bastard Programmer from Hell
If you can't read my code, try converting it here[^]
|
|
|
|
|
Hi Eddy, thanks for commenting ! I'm kind of a "hermit" in a tropical country (by choice) and technical feedback from my peers and betters is just "catnip" to me
As I commented to Richard, the 'get only "automatic property" is legal syntax in .NET; it just means it is de facto 'readonly. Whether that's good style, or code smell ? I haven't really thought about. What do you think ?Eddy Vluggen wrote: Must have missed passing the exception as a second argument to pass it as an inner-exception. Oh yeah, that is a bad thing to leave out.
cheers, Bill
«To kill an error's as good a service, sometimes better than, establishing new truth or fact.» Charles Darwin in "Prospero's Precepts"
|
|
|
|
|
BillWoodruff wrote: I'm kind of a "hermit" in a tropical country Any tips on how to become one?
BillWoodruff wrote: is legal syntax in .NET The compiler said "Property or indexer 'YourNameSpace.MyModel.FilePath' cannot be assigned to -- it is read only". Didn't check to see if it was a warning that is treated as an error, or an actual error.
Bastard Programmer from Hell
If you can't read my code, try converting it here[^]
|
|
|
|
|
Hi Eddy, in my reply to Richard above I explain that (surprise) using VS 2015 CTP 6 compiling with FrameWork 4.5 allows a 'get only auto-property: no error, no compiler warning. So there's a compiler difference, evidently (?). My bad.
Becoming a hermit ... ahhh ... well you just have to cut the anchor-chains, unfurl your sails, and cast--off
cheers, Bill
«To kill an error's as good a service, sometimes better than, establishing new truth or fact.» Charles Darwin in "Prospero's Precepts"
|
|
|
|
|
BillWoodruff wrote: So there's a compiler difference, evidently (?). My bad. No bad, just an observed difference. Sounds like good news actually, means I won't have to add a backing-field manually anymore
BillWoodruff wrote: Becoming a hermit ... ahhh ... well you just have to cut the anchor-chains, unfurl your sails, and cast--off If I did that, I'd end up on the streets
Bastard Programmer from Hell
If you can't read my code, try converting it here[^]
|
|
|
|
|
code clicks picture but doesnt save it. getting blanks image as output.
i want to click picture and save it on start up of OS! Newbie is here.. HELP Frown | :(
SAME CODE WORKS ON BTN CLICK BUT DOESNT ON WINFORM LOAD!
picBOx1.Image.Save(fs, System.Drawing.Imaging.ImageFormat.Jpg);
|
|
|
|
|
Member 11388199 wrote: i want to click picture and save it on start up of OS! How can you do that, since you have no control until after the OS has finished loading and you have logged on. You need to show the code that does not work and explain exactly what happens. Are you sure the image is available when you try to save it? Do you check the return values to see if the method call succeeded? Do you trap exceptions and report them? etc.
|
|
|
|
|
Quote: it works on btn click just wanted make it on form load..
or any other way to click picture "after" OS started.
I'm working on project that tracks lost laptop.
Exceptions->
An error occurred while capturing the video image. The video capture will now be terminated.
Object reference not set to an instance of an object.
HERE IS THE WHOLE CODE..
public partial class mainWinForm : Form
{
public mainWinForm()
{
InitializeComponent();
}
WebCam webcam;
private void mainWinForm_Load(object sender, EventArgs e)
{
webcam = new WebCam();
webcam.InitializeWebCam(ref imgVideo);
webcam.Start();
imgCapture.Image = imgVideo.Image;
Helper.SaveImageCapture(imgCapture.Image);
}
private void bntStart_Click(object sender, EventArgs e)
{
webcam.Start();
}
private void bntStop_Click(object sender, EventArgs e)
{
webcam.Stop();
}
private void bntContinue_Click(object sender, EventArgs e)
{
webcam.Continue();
}
private void bntCapture_Click(object sender, EventArgs e)
{
webcam.Start();
imgCapture.Image = imgVideo.Image;
}
private void bntSave_Click(object sender, EventArgs e)
{
webcam.Start();
imgCapture.Image = imgVideo.Image;
Helper.SaveImageCapture(imgCapture.Image);
}
private void bntVideoFormat_Click(object sender, EventArgs e)
{
webcam.ResolutionSetting();
}
private void bntVideoSource_Click(object sender, EventArgs e)
{
webcam.AdvanceSetting();
}
}
|
|
|
|
|
Make what work on FormLoad? And what does any of this have to do with video capture?
There is clearly a bug in your code, but without seeing it no one can guess what you are doing wrong. Please try and explain your problem in proper detail.
|
|
|
|
|